diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6d4909716..1773c654a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -129,7 +129,7 @@ libs download test:
- changes:
- libs/download/**/*
script:
- - ./gradlew :libs:download:testDebugUnitTest
+ - ./gradlew :libs:download:testAndroidHostTest
libs index test:
<<: *test-template
@@ -138,7 +138,7 @@ libs index test:
- changes:
- libs/index/**/*
script:
- - ./gradlew :libs:index:testDebugUnitTest
+ - ./gradlew :libs:index:testAndroidHostTest
app lint:
<<: *test-template
@@ -176,7 +176,7 @@ libs lint:
- changes:
- libs/**/*
script:
- - ./gradlew :libs:core:lint :libs:database:lint :libs:download:lint :libs:index:lint :libs:sharedTest:lint
+ - ./gradlew :libs:core:lint :libs:database:lint :libs:download:lint :libs:index:lint
- ./gradlew :libs:core:ktfmtCheck :libs:database:ktfmtCheck :libs:download:ktfmtCheck :libs:index:ktfmtCheck :libs:sharedTest:ktfmtCheck
- ./gradlew checkLegacyAbi
@@ -332,9 +332,9 @@ libs database schema:
- ./gradlew :app:installBasicDefaultDebug :legacy:installFullDebug
- adb shell am start -n org.fdroid.fdroid.debug/org.fdroid.fdroid.views.main.MainActivity
- export FLAG="-Pandroid.testInstrumentationRunnerArguments.notAnnotation=androidx.test.filters.LargeTest,androidx.test.filters.FlakyTest"
- - ./gradlew $FLAG :app:connectedBasicDebugAndroidTest :legacy:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedCheck :libs:index:connectedCheck
+ - ./gradlew $FLAG :app:connectedBasicDebugAndroidTest :legacy:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedAndroidTest :libs:index:connectedAndroidTest
- export FLAG="-Pandroid.testInstrumentationRunnerArguments.annotation=androidx.test.filters.FlakyTest"
- - for i in {1..5}; do echo "$i" && ./gradlew $FLAG :app:connectedBasicDebugAndroidTest :legacy:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedCheck :libs:index:connectedCheck && break; done || exit 137
+ - for i in {1..5}; do echo "$i" && ./gradlew $FLAG :app:connectedBasicDebugAndroidTest :legacy:connectedFullDebugAndroidTest :libs:database:connectedCheck :libs:download:connectedAndroidTest :libs:index:connectedAndroidTest && break; done || exit 137
allow_failure:
exit_codes: 137
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3acafff00..596785d1f 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,7 +4,6 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.android.ksp)
alias(libs.plugins.android.hilt)
- alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.plugin.serialization)
alias(libs.plugins.jetbrains.compose.compiler)
alias(libs.plugins.screenshot)
diff --git a/app/src/androidTest/java/org/fdroid/download/DnsCacheTest.kt b/app/src/androidTest/java/org/fdroid/download/DnsCacheTest.kt
index e0e9973f1..137e396b1 100644
--- a/app/src/androidTest/java/org/fdroid/download/DnsCacheTest.kt
+++ b/app/src/androidTest/java/org/fdroid/download/DnsCacheTest.kt
@@ -4,12 +4,12 @@ import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import java.net.InetAddress
-import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
import org.fdroid.settings.SettingsManager
+import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
diff --git a/app/src/test/java/org/fdroid/history/HistoryManagerTest.kt b/app/src/test/java/org/fdroid/history/HistoryManagerTest.kt
index eb9bb591d..9c1911bac 100644
--- a/app/src/test/java/org/fdroid/history/HistoryManagerTest.kt
+++ b/app/src/test/java/org/fdroid/history/HistoryManagerTest.kt
@@ -8,10 +8,10 @@ import io.mockk.mockk
import io.mockk.verify
import java.io.FileOutputStream
import kotlin.random.Random
-import kotlin.test.Test
import kotlin.test.assertEquals
import org.fdroid.settings.SettingsManager
import org.junit.Rule
+import org.junit.Test
import org.junit.rules.TemporaryFolder
private const val MAX_EVENTS = 10
diff --git a/build.gradle b/build.gradle
index 2342cd566..6b1486003 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,6 @@ plugins {
alias libs.plugins.android.library apply false
alias libs.plugins.android.ksp apply false
alias libs.plugins.android.hilt apply false
- alias libs.plugins.jetbrains.kotlin.android apply false
alias libs.plugins.jetbrains.kotlin.multiplatform apply false
alias libs.plugins.jetbrains.kotlin.plugin.serialization apply false
alias libs.plugins.jetbrains.compose.compiler apply false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index c5a1f6d24..6a0c3bd59 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,9 +1,9 @@
[versions]
compileSdk = "36"
kotlin = "2.3.10"
-androidGradlePlugin = "8.13.2"
+androidGradlePlugin = "9.1.0"
androidKspPlugin = "2.3.4"
-hilt = "2.57.2"
+hilt = "2.59.2"
hiltWork = "1.3.0"
hiltNavigationCompose = "1.3.0"
dokka = "2.1.0"
@@ -20,7 +20,7 @@ okhttp = "4.12.0"
room = "2.8.4"
glide = "5.0.5"
glideCompose = "1.0.0-beta08"
-coilCompose = "3.3.0"
+coilCompose = "3.4.0"
molecule = "2.2.0"
hints = "2.0.1"
composePreference = "2.1.1"
@@ -38,7 +38,7 @@ androidxConstraintlayout = "2.2.1"
androidxSwipeRefreshLayout = "1.2.0"
androidxVectordrawable = "1.2.0"
androidxGridlayout = "1.1.0"
-androidxComposeBom = "2026.02.00"
+androidxComposeBom = "2026.02.01"
androidxActivityCompose = "1.12.4"
accompanistDrawablepainter = "0.37.3"
@@ -85,7 +85,7 @@ androidxTestUiautomator = "2.3.0"
androidxTestMonitor = "1.8.0"
mockitoCore = "5.1.1"
hamcrest = "2.2"
-goncalossilvaResources = "0.2.1"
+goncalossilvaResources = "0.15.0"
turbine = "1.2.1"
json = "20220320"
@@ -209,7 +209,6 @@ android-multiplatform-library = { id = "com.android.kotlin.multiplatform.library
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
android-ksp = { id = "com.google.devtools.ksp", version.ref = "androidKspPlugin" }
android-hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrains-kotlin-plugin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrains-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml
index e0123177f..31c596ba0 100644
--- a/gradle/verification-metadata.xml
+++ b/gradle/verification-metadata.xml
@@ -183,257 +183,31 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -449,26 +223,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -479,154 +238,31 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -634,46 +270,12 @@
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -681,609 +283,39 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -1291,54 +323,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -1346,1354 +333,119 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -2716,101 +468,19 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -2823,61 +493,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -2888,26 +503,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -2915,18 +510,12 @@
-
-
-
-
+
-
-
-
-
+
@@ -2939,138 +528,29 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
+
@@ -3080,10 +560,7 @@
-
-
-
-
+
@@ -3093,7 +570,7 @@
-
+
@@ -3101,82 +578,24 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -3189,11 +608,6 @@
-
-
-
-
-
@@ -3204,22 +618,14 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -3259,85 +665,17 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -3345,1048 +683,104 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
@@ -4394,14 +788,6 @@
-
-
-
-
-
-
-
-
@@ -4417,72 +803,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -4495,10 +835,7 @@
-
-
-
-
+
@@ -4506,42 +843,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -4549,743 +858,86 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -5296,348 +948,91 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -5645,7 +1040,7 @@
-
+
@@ -5663,19 +1058,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -5688,10 +1070,7 @@
-
-
-
-
+
@@ -5701,39 +1080,22 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
-
-
-
+
-
-
-
-
+
@@ -5741,11 +1103,6 @@
-
-
-
-
-
@@ -5756,533 +1113,79 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -6290,1590 +1193,269 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
@@ -7886,291 +1468,54 @@
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -8178,67 +1523,14 @@
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -8246,354 +1538,64 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -8606,978 +1608,194 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -9585,462 +1803,114 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -10048,90 +1918,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10144,92 +1940,24 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10237,75 +1965,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10322,47 +1993,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -10372,7 +2010,6 @@
-
@@ -10381,84 +2018,49 @@
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -10466,55 +2068,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -10523,99 +2083,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10626,25 +2103,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10660,37 +2123,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10706,69 +2148,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10777,18 +2163,15 @@
-
-
-
+
+
+
-
-
-
@@ -10800,68 +2183,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10872,14 +2198,6 @@
-
-
-
-
-
-
-
-
@@ -10895,79 +2213,23 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -10975,22 +2237,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11013,43 +2262,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11061,27 +2273,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11099,12 +2293,9 @@
-
-
-
-
-
-
+
+
+
@@ -11112,24 +2303,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11137,19 +2313,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11157,19 +2323,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11177,19 +2333,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11197,24 +2343,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11222,19 +2353,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -11242,111 +2363,31 @@
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11354,13 +2395,11 @@
-
-
+
-
@@ -11369,12 +2408,6 @@
-
-
-
-
-
-
@@ -11385,249 +2418,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -11637,78 +2435,34 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11724,106 +2478,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11839,144 +2503,36 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -11987,60 +2543,24 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
@@ -12048,76 +2568,20 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12128,39 +2592,25 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
@@ -12173,15 +2623,9 @@
-
-
-
-
-
-
-
-
-
+
+
+
@@ -12194,59 +2638,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12257,26 +2658,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12287,26 +2668,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12322,26 +2683,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12352,26 +2693,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12382,26 +2703,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12417,26 +2718,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -12452,213 +2733,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -12666,1520 +2748,89 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -14187,1023 +2838,106 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15214,21 +2948,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15239,21 +2958,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15264,21 +2968,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15289,21 +2978,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15314,21 +2988,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15339,21 +2998,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15364,21 +3008,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15389,21 +3018,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15414,51 +3028,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15474,41 +3053,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15519,73 +3073,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15596,262 +3095,54 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -15859,40 +3150,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -15905,201 +3168,39 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -16107,65 +3208,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16175,47 +3217,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16226,9 +3227,6 @@
-
-
-
@@ -16245,150 +3243,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16399,57 +3253,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16460,72 +3268,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16541,31 +3288,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16583,38 +3310,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16650,201 +3348,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16855,74 +3368,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16931,49 +3378,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -16981,101 +3395,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -17085,60 +3440,24 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -17154,19 +3473,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -17174,1643 +3483,156 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -18821,284 +3643,39 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -19106,687 +3683,81 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -19808,181 +3779,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -19998,116 +3809,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -20123,126 +3839,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -20251,545 +3862,51 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -20800,72 +3917,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -20876,57 +3927,11 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -20947,359 +3952,61 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -21310,246 +4017,36 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -21565,51 +4062,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -21620,16 +4087,6 @@
-
-
-
-
-
-
-
-
-
-
@@ -21640,71 +4097,26 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -21712,208 +4124,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -21921,47 +4167,9 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -21969,65 +4177,24 @@
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
@@ -22035,12 +4202,6 @@
-
-
-
-
-
-
@@ -22051,776 +4212,81 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -22831,82 +4297,16 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -22928,27 +4328,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6048b9914..7abf69a2f 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
#Tue Oct 22 15:45:27 PDT 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionSha256Sum=d7042b3c11565c192041fc8c4703f541b888286404b4f267138c1d094d8ecdca
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-all.zip
+distributionSha256Sum=17f277867f6914d61b1aa02efab1ba7bb439ad652ca485cd8ca6842fccec6e43
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/legacy/build.gradle b/legacy/build.gradle
index 46fdd0a4c..7d6d87c97 100644
--- a/legacy/build.gradle
+++ b/legacy/build.gradle
@@ -1,6 +1,8 @@
+import com.android.build.api.variant.ResValue
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+
plugins {
id 'com.android.application'
- id 'kotlin-android'
id 'org.jetbrains.kotlin.plugin.compose'
}
@@ -52,10 +54,11 @@ android {
minifyEnabled true
shrinkResources true
buildConfigField "String", "PRIVILEGED_EXTENSION_PACKAGE_NAME", privilegedExtensionApplicationId
- buildConfigField "String", "ACRA_REPORT_EMAIL", '"reports@f-droid.org"' // String needs both quotes
+ buildConfigField "String", "ACRA_REPORT_EMAIL", '"reports@f-droid.org"'
+ // String needs both quotes
buildConfigField "String", "ACRA_REPORT_FILE_NAME", '"ACRA-report.stacktrace.json"'
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ testProguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
}
debug {
getIsDefault().set(true)
@@ -63,7 +66,7 @@ android {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
// testProguardFiles gets partially ignored for instrumentation tests
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'src/androidTest/proguard-rules.pro'
println 'buildTypes.debug defaultConfig.versionCode ' + defaultConfig.versionCode
}
}
@@ -81,8 +84,14 @@ android {
applicationIdSuffix ".basic"
}
}
- applicationVariants.configureEach { variant ->
- variant.resValue "string", "applicationId", variant.applicationId
+
+ androidComponents {
+ onVariants(selector().all()) { variant ->
+ variant.resValues.put(
+ variant.makeResValueKey("string", "applicationId"),
+ new ResValue(variant.applicationId.get(), null)
+ )
+ }
}
compileOptions {
@@ -91,8 +100,10 @@ android {
targetCompatibility JavaVersion.VERSION_17
}
- kotlinOptions {
- jvmTarget = '17'
+ kotlin {
+ compilerOptions {
+ jvmTarget = JvmTarget.JVM_17
+ }
}
aaptOptions {
@@ -101,6 +112,7 @@ android {
buildFeatures {
buildConfig true
+ resValues = true
compose true
aidl true
}
diff --git a/legacy/src/androidTest/java/org/fdroid/fdroid/net/DnsCacheTest.kt b/legacy/src/androidTest/java/org/fdroid/fdroid/net/DnsCacheTest.kt
index 56e8d9891..22c2e3bbd 100644
--- a/legacy/src/androidTest/java/org/fdroid/fdroid/net/DnsCacheTest.kt
+++ b/legacy/src/androidTest/java/org/fdroid/fdroid/net/DnsCacheTest.kt
@@ -1,10 +1,10 @@
package org.fdroid.fdroid.net
-import org.fdroid.fdroid.Preferences
-import org.junit.Assert
import java.net.InetAddress
import java.util.Arrays
-import kotlin.test.Test
+import org.fdroid.fdroid.Preferences
+import org.junit.Assert
+import org.junit.Test
class DnsCacheTest {
diff --git a/legacy/src/androidTest/java/org/fdroid/fdroid/work/AppUpdateWorkerTest.kt b/legacy/src/androidTest/java/org/fdroid/fdroid/work/AppUpdateWorkerTest.kt
index 6e46e2ad8..7667b7a79 100644
--- a/legacy/src/androidTest/java/org/fdroid/fdroid/work/AppUpdateWorkerTest.kt
+++ b/legacy/src/androidTest/java/org/fdroid/fdroid/work/AppUpdateWorkerTest.kt
@@ -18,6 +18,10 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.verify
+import java.io.IOException
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
import kotlinx.coroutines.runBlocking
import org.fdroid.fdroid.AppUpdateManager
import org.fdroid.fdroid.FDroidApp
@@ -31,12 +35,8 @@ import org.fdroid.fdroid.work.AppUpdateWorker.Companion.UNIQUE_WORK_NAME_APP_UPD
import org.fdroid.fdroid.work.AppUpdateWorker.Companion.UNIQUE_WORK_NAME_AUTO_APP_UPDATE
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
-import java.io.IOException
-import kotlin.test.Test
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
class AppUpdateWorkerTest {
diff --git a/libs/core/api/jvm/core.api b/libs/core/api/jvm/core.api
new file mode 100644
index 000000000..4af8b61f5
--- /dev/null
+++ b/libs/core/api/jvm/core.api
@@ -0,0 +1,8 @@
+public abstract interface class org/fdroid/IndexFile {
+ public abstract fun getIpfsCidV1 ()Ljava/lang/String;
+ public abstract fun getName ()Ljava/lang/String;
+ public abstract fun getSha256 ()Ljava/lang/String;
+ public abstract fun getSize ()Ljava/lang/Long;
+ public abstract fun serialize ()Ljava/lang/String;
+}
+
diff --git a/libs/core/build.gradle.kts b/libs/core/build.gradle.kts
index 1c209215c..955d12a04 100644
--- a/libs/core/build.gradle.kts
+++ b/libs/core/build.gradle.kts
@@ -12,11 +12,11 @@ kotlin {
abiValidation { enabled = true }
compilerOptions { optIn.add("kotlin.RequiresOptIn") }
+ jvm()
android {
namespace = "org.fdroid.core"
compileSdk = libs.versions.compileSdk.get().toInt()
minSdk = 21
- withJava()
withHostTestBuilder {}.configure {}
withDeviceTestBuilder { sourceSetTreeName = "test" }
compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 }
diff --git a/libs/database/build.gradle.kts b/libs/database/build.gradle.kts
index 2f5cdc3cf..77c59a0eb 100644
--- a/libs/database/build.gradle.kts
+++ b/libs/database/build.gradle.kts
@@ -1,5 +1,4 @@
plugins {
- alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.android.library)
alias(libs.plugins.android.ksp)
alias(libs.plugins.jetbrains.dokka)
@@ -26,14 +25,14 @@ android {
}
sourceSets {
getByName("androidTest") {
- java.srcDirs("src/dbTest/java")
+ kotlin.directories += "src/dbTest/java"
// Adds exported schema location as test app assets.
- assets.srcDirs(files("$projectDir/schemas"))
+ assets.directories += "$projectDir/schemas"
}
getByName("test") {
- java.srcDirs("src/dbTest/java")
+ kotlin.directories += "src/dbTest/java"
// Adds exported schema location as test app assets.
- assets.srcDirs(files("$projectDir/schemas"))
+ assets.directories += "$projectDir/schemas"
}
}
compileOptions {
@@ -41,7 +40,6 @@ android {
targetCompatibility = JavaVersion.VERSION_17
}
testOptions {
- targetSdk = 34 // relevant for instrumentation tests (targetSdk 21 fails on Android 14)
unitTests { isIncludeAndroidResources = true }
}
androidResources {
@@ -60,8 +58,6 @@ android {
kotlin {
explicitApi()
- @OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
- abiValidation { enabled = true }
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
optIn.add("kotlin.RequiresOptIn")
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt
index 977ab4d57..55c9f4a90 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt
@@ -2,6 +2,9 @@ package org.fdroid.database
import androidx.core.os.LocaleListCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.database.TestUtils.toMetadataV2
import org.fdroid.test.TestRepoUtils.getRandomRepo
@@ -9,215 +12,212 @@ import org.fdroid.test.TestUtils.sort
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class AppDaoTest : AppTest() {
- @Test
- fun insertGetDeleteSingleApp() {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1)
+ @Test
+ fun insertGetDeleteSingleApp() {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1)
- assertEquals(app1, appDao.getApp(repoId, packageName)?.toMetadataV2()?.sort())
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ assertEquals(app1, appDao.getApp(repoId, packageName)?.toMetadataV2()?.sort())
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- appDao.deleteAppMetadata(repoId, packageName)
- assertEquals(0, appDao.countApps())
- assertEquals(0, appDao.countLocalizedFiles())
- assertEquals(0, appDao.countLocalizedFileLists())
- }
+ appDao.deleteAppMetadata(repoId, packageName)
+ assertEquals(0, appDao.countApps())
+ assertEquals(0, appDao.countLocalizedFiles())
+ assertEquals(0, appDao.countLocalizedFileLists())
+ }
- @Test
- fun testGetSameAppFromTwoRepos() {
- // insert same app into three repos (repoId1 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId3, packageName, app3, locales)
+ @Test
+ fun testGetSameAppFromTwoRepos() {
+ // insert same app into three repos (repoId1 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId3, packageName, app3, locales)
- // ensure expected repo weights
- val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
- val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
- val repoPrefs3 = repoDao.getRepositoryPreferences(repoId3) ?: fail()
- assertTrue(repoPrefs2.weight < repoPrefs3.weight)
- assertTrue(repoPrefs3.weight < repoPrefs1.weight)
+ // ensure expected repo weights
+ val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
+ val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
+ val repoPrefs3 = repoDao.getRepositoryPreferences(repoId3) ?: fail()
+ assertTrue(repoPrefs2.weight < repoPrefs3.weight)
+ assertTrue(repoPrefs3.weight < repoPrefs1.weight)
- // each app gets returned as stored from each repo
- assertEquals(app1, appDao.getApp(repoId1, packageName)?.toMetadataV2()?.sort())
- assertEquals(app2, appDao.getApp(repoId2, packageName)?.toMetadataV2()?.sort())
- assertEquals(app3, appDao.getApp(repoId3, packageName)?.toMetadataV2()?.sort())
+ // each app gets returned as stored from each repo
+ assertEquals(app1, appDao.getApp(repoId1, packageName)?.toMetadataV2()?.sort())
+ assertEquals(app2, appDao.getApp(repoId2, packageName)?.toMetadataV2()?.sort())
+ assertEquals(app3, appDao.getApp(repoId3, packageName)?.toMetadataV2()?.sort())
- // if repo is not given, app from repo with highest weight is returned
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // if repo is not given, app from repo with highest weight is returned
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // clear all apps
- appDao.clearAll()
- assertEquals(0, appDao.countApps())
- assertEquals(0, appDao.countLocalizedFiles())
- assertEquals(0, appDao.countLocalizedFileLists())
- }
+ // clear all apps
+ appDao.clearAll()
+ assertEquals(0, appDao.countApps())
+ assertEquals(0, appDao.countLocalizedFiles())
+ assertEquals(0, appDao.countLocalizedFileLists())
+ }
- @Test
- fun testAppRepoPref() {
- // insert same app into three repos (repoId1 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId3, packageName, app3, locales)
+ @Test
+ fun testAppRepoPref() {
+ // insert same app into three repos (repoId1 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId3, packageName, app3, locales)
- // app from repo with highest weight is returned, if no prefs are set
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // app from repo with highest weight is returned, if no prefs are set
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // prefer repo3 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
- assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // prefer repo3 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
+ assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // prefer repo1 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // prefer repo1 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // preferring non-existent repo for this app makes query fall back to highest weight repo
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = 1337L))
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- }
+ // preferring non-existent repo for this app makes query fall back to highest weight repo
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = 1337L))
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ }
- @Test
- fun testGetSameAppFromTwoReposOneDisabled() {
- // insert same app into two repos (repoId2 has highest weight)
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
+ @Test
+ fun testGetSameAppFromTwoReposOneDisabled() {
+ // insert same app into two repos (repoId2 has highest weight)
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
- // app from repo with highest weight gets returned
- assertEquals(app2, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // app from repo with highest weight gets returned
+ assertEquals(app2, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // repo with highest weight gets disabled
- repoDao.setRepositoryEnabled(repoId2, false)
+ // repo with highest weight gets disabled
+ repoDao.setRepositoryEnabled(repoId2, false)
- // now app from repo with lower weight is returned
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- }
+ // now app from repo with lower weight is returned
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ }
- @Test
- fun testGetRepositoryIdsForApp() {
- // initially, the app is in no repos
- assertEquals(emptyList(), appDao.getRepositoryIdsForApp(packageName))
+ @Test
+ fun testGetRepositoryIdsForApp() {
+ // initially, the app is in no repos
+ assertEquals(emptyList(), appDao.getRepositoryIdsForApp(packageName))
- // insert same app into one repo
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- assertEquals(listOf(repoId1), appDao.getRepositoryIdsForApp(packageName))
+ // insert same app into one repo
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ assertEquals(listOf(repoId1), appDao.getRepositoryIdsForApp(packageName))
- // insert the app into one more repo
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, app2, locales)
- assertEquals(listOf(repoId1, repoId2), appDao.getRepositoryIdsForApp(packageName))
+ // insert the app into one more repo
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, app2, locales)
+ assertEquals(listOf(repoId1, repoId2), appDao.getRepositoryIdsForApp(packageName))
- // when repo1 is disabled, it doesn't get returned anymore
- repoDao.setRepositoryEnabled(repoId1, false)
- assertEquals(listOf(repoId2), appDao.getRepositoryIdsForApp(packageName))
- }
+ // when repo1 is disabled, it doesn't get returned anymore
+ repoDao.setRepositoryEnabled(repoId1, false)
+ assertEquals(listOf(repoId2), appDao.getRepositoryIdsForApp(packageName))
+ }
- @Test
- fun testUpdateCompatibility() {
- // insert two apps with one version each
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
+ @Test
+ fun testUpdateCompatibility() {
+ // insert two apps with one version each
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
- // without versions, app isn't compatible
- assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
- appDao.updateCompatibility(repoId)
- assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
+ // without versions, app isn't compatible
+ assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
+ appDao.updateCompatibility(repoId)
+ assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
- // still incompatible with incompatible version
- versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(), false)
- appDao.updateCompatibility(repoId)
- assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
+ // still incompatible with incompatible version
+ versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(), false)
+ appDao.updateCompatibility(repoId)
+ assertEquals(false, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
- // only with at least one compatible version, the app becomes compatible
- versionDao.insert(repoId, packageName, "2", getRandomPackageVersionV2(), true)
- appDao.updateCompatibility(repoId)
- assertEquals(true, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
- }
+ // only with at least one compatible version, the app becomes compatible
+ versionDao.insert(repoId, packageName, "2", getRandomPackageVersionV2(), true)
+ appDao.updateCompatibility(repoId)
+ assertEquals(true, appDao.getApp(repoId, packageName)?.metadata?.isCompatible)
+ }
- @Test
- fun testAfterLocalesChanged() {
- // insert app with German and French locales
- val localesBefore = LocaleListCompat.forLanguageTags("de-DE")
- val app = app1.copy(
- name = mapOf("de-DE" to "de-DE", "fr-FR" to "fr-FR"),
- summary = mapOf("de-DE" to "de-DE", "fr-FR" to "fr-FR"),
- )
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app, localesBefore)
+ @Test
+ fun testAfterLocalesChanged() {
+ // insert app with German and French locales
+ val localesBefore = LocaleListCompat.forLanguageTags("de-DE")
+ val app =
+ app1.copy(
+ name = mapOf("de-DE" to "de-DE", "fr-FR" to "fr-FR"),
+ summary = mapOf("de-DE" to "de-DE", "fr-FR" to "fr-FR"),
+ )
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app, localesBefore)
- // device is set to German, so name and summary come out German
- val appBefore = appDao.getApp(repoId, packageName)
- assertEquals("de-DE", appBefore?.name)
- assertEquals("de-DE", appBefore?.summary)
+ // device is set to German, so name and summary come out German
+ val appBefore = appDao.getApp(repoId, packageName)
+ assertEquals("de-DE", appBefore?.name)
+ assertEquals("de-DE", appBefore?.summary)
- // device gets switched to French
- val localesAfter = LocaleListCompat.forLanguageTags("fr-FR")
- db.afterLocalesChanged(localesAfter)
+ // device gets switched to French
+ val localesAfter = LocaleListCompat.forLanguageTags("fr-FR")
+ db.afterLocalesChanged(localesAfter)
- // device is set to French now, so name and summary come out French
- val appAfter = appDao.getApp(repoId, packageName)
- assertEquals("fr-FR", appAfter?.name)
- assertEquals("fr-FR", appAfter?.summary)
- }
+ // device is set to French now, so name and summary come out French
+ val appAfter = appDao.getApp(repoId, packageName)
+ assertEquals("fr-FR", appAfter?.name)
+ assertEquals("fr-FR", appAfter?.summary)
+ }
- @Test
- fun testGetNumberOfAppsInCategory() {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
+ @Test
+ fun testGetNumberOfAppsInCategory() {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
- // app1 is in A and B
- appDao.insert(repoId, packageName1, app1, locales)
- assertEquals(1, appDao.getNumberOfAppsInCategory("A"))
- assertEquals(1, appDao.getNumberOfAppsInCategory("B"))
- assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
+ // app1 is in A and B
+ appDao.insert(repoId, packageName1, app1, locales)
+ assertEquals(1, appDao.getNumberOfAppsInCategory("A"))
+ assertEquals(1, appDao.getNumberOfAppsInCategory("B"))
+ assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
- // app2 is in A
- appDao.insert(repoId, packageName2, app2, locales)
- assertEquals(2, appDao.getNumberOfAppsInCategory("A"))
- assertEquals(1, appDao.getNumberOfAppsInCategory("B"))
- assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
+ // app2 is in A
+ appDao.insert(repoId, packageName2, app2, locales)
+ assertEquals(2, appDao.getNumberOfAppsInCategory("A"))
+ assertEquals(1, appDao.getNumberOfAppsInCategory("B"))
+ assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
- // app3 is in A and B
- appDao.insert(repoId, packageName3, app3, locales)
- assertEquals(3, appDao.getNumberOfAppsInCategory("A"))
- assertEquals(2, appDao.getNumberOfAppsInCategory("B"))
- assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
+ // app3 is in A and B
+ appDao.insert(repoId, packageName3, app3, locales)
+ assertEquals(3, appDao.getNumberOfAppsInCategory("A"))
+ assertEquals(2, appDao.getNumberOfAppsInCategory("B"))
+ assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
- // app1 as a variant of app2 in another repo will NOT show one more app in B
- // because repo2 has less priority, so repo1 is preferred which doesn't have app1 in B
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName2, app1, locales)
- assertEquals(3, appDao.getNumberOfAppsInCategory("A"))
- assertEquals(2, appDao.getNumberOfAppsInCategory("B"))
- assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
- }
+ // app1 as a variant of app2 in another repo will NOT show one more app in B
+ // because repo2 has less priority, so repo1 is preferred which doesn't have app1 in B
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName2, app1, locales)
+ assertEquals(3, appDao.getNumberOfAppsInCategory("A"))
+ assertEquals(2, appDao.getNumberOfAppsInCategory("B"))
+ assertEquals(0, appDao.getNumberOfAppsInCategory("C"))
+ }
- @Test
- fun testGetNumberOfAppsInRepository() {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- assertEquals(0, appDao.getNumberOfAppsInRepository(repoId))
+ @Test
+ fun testGetNumberOfAppsInRepository() {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ assertEquals(0, appDao.getNumberOfAppsInRepository(repoId))
- appDao.insert(repoId, packageName1, app1, locales)
- assertEquals(1, appDao.getNumberOfAppsInRepository(repoId))
+ appDao.insert(repoId, packageName1, app1, locales)
+ assertEquals(1, appDao.getNumberOfAppsInRepository(repoId))
- appDao.insert(repoId, packageName2, app2, locales)
- assertEquals(2, appDao.getNumberOfAppsInRepository(repoId))
-
- appDao.insert(repoId, packageName3, app3, locales)
- assertEquals(3, appDao.getNumberOfAppsInRepository(repoId))
- }
+ appDao.insert(repoId, packageName2, app2, locales)
+ assertEquals(2, appDao.getNumberOfAppsInRepository(repoId))
+ appDao.insert(repoId, packageName3, app3, locales)
+ assertEquals(3, appDao.getNumberOfAppsInRepository(repoId))
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt
index d25bae15f..6dfaf67a7 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/AppListItemsTest.kt
@@ -6,6 +6,14 @@ import androidx.core.content.pm.PackageInfoCompat
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockk
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.fdroid.LocaleChooser.getBestLocale
@@ -19,833 +27,800 @@ import org.fdroid.test.TestUtils.getRandomString
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
@Suppress("DEPRECATION")
@RunWith(AndroidJUnit4::class)
internal class AppListItemsTest : AppTest() {
- private val pm: PackageManager = mockk()
+ private val pm: PackageManager = mockk()
- private val appPairs = listOf(
- Pair(packageName1, app1),
- Pair(packageName2, app2),
- Pair(packageName3, app3),
- )
+ private val appPairs =
+ listOf(Pair(packageName1, app1), Pair(packageName2, app2), Pair(packageName3, app3))
- @Test
- fun testSearchQuery() {
- val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
- val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
- val app3 = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
- // insert three apps in a random order
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName3, app3, locales)
- appDao.insert(repoId, packageName2, app2, locales)
+ @Test
+ fun testSearchQuery() {
+ val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
+ val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
+ val app3 = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
+ // insert three apps in a random order
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
- // one of the apps is installed
- val packageInfo2 = PackageInfo().apply {
- packageName = packageName2
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
+ // one of the apps is installed
+ val packageInfo2 =
+ PackageInfo().apply {
+ packageName = packageName2
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
- // get first app by search, sort order doesn't matter
- appDao.getAppListItems(pm, "One", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // get first app by partial search, sort by name
- appDao.getAppListItems(pm, "On", NAME).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // get second app by search, sort order doesn't matter
- appDao.getAppListItems(pm, "Two", NAME).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2, apps[0])
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo2),
- apps[0].installedVersionCode
- )
- assertEquals(packageInfo2.versionName, apps[0].installedVersionName)
- }
-
- // get second and third app by searching for summary
- appDao.getAppListItems(pm, "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(2, apps.size)
- // sort-order isn't fixes, yet
- if (apps[0].packageName == packageName2) {
- assertEquals(app2, apps[0])
- assertEquals(app3, apps[1])
- } else {
- assertEquals(app3, apps[0])
- assertEquals(app2, apps[1])
- }
- }
-
- // empty search for unknown search term
- appDao.getAppListItems(pm, "foo bar", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
+ // get first app by search, sort order doesn't matter
+ appDao.getAppListItems(pm, "One", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testSearchQueryInCategory() {
- val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
- val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
- val app3 = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
- // insert three apps in a random order
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName3, app3, locales)
- appDao.insert(repoId, packageName2, app2, locales)
-
- // one of the apps is installed
- val packageInfo2 = PackageInfo().apply {
- packageName = packageName2
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
-
- // get first app by search, sort order doesn't matter
- appDao.getAppListItems(pm, "A", "One", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // get second app by search, sort order doesn't matter
- appDao.getAppListItems(pm, "A", "Two", NAME).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2, apps[0])
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo2),
- apps[0].installedVersionCode
- )
- assertEquals(packageInfo2.versionName, apps[0].installedVersionName)
- }
-
- // get second and third app by searching for summary
- appDao.getAppListItems(pm, "A", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(2, apps.size)
- // sort-order isn't fixes, yet
- if (apps[0].packageName == packageName2) {
- assertEquals(app2, apps[0])
- assertEquals(app3, apps[1])
- } else {
- assertEquals(app3, apps[0])
- assertEquals(app2, apps[1])
- }
- }
-
- // get third app by searching for summary in category B only
- appDao.getAppListItems(pm, "B", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app3, apps[0])
- }
-
- // empty search for unknown category
- appDao.getAppListItems(pm, "C", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
-
- // empty search for unknown search term
- appDao.getAppListItems(pm, "A", "foo bar", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
+ // get first app by partial search, sort by name
+ appDao.getAppListItems(pm, "On", NAME).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testSearchQueryPerRepo() {
- val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
- val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
- val app3a = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
- val app3b = app3.copy(name = mapOf("en-US" to "Three"), summary = mapOf("en" to "summary"))
- // insert three apps in a random order
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName1, app1, locales)
- appDao.insert(repoId2, packageName2, app2, locales)
- appDao.insert(repoId2, packageName3, app3a, locales)
- appDao.insert(repoId3, packageName3, app3b, locales)
-
- // one of the apps is installed
- val packageInfo2 = PackageInfo().apply {
- packageName = packageName2
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
-
- // get all apps in repo2 sorted by last updated
- appDao.getAppListItems(pm, repoId2, null, LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(2, apps.size)
- assertEquals(app3a, apps[0])
- assertEquals(app2, apps[1])
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo2),
- apps[1].installedVersionCode
- )
- assertEquals(packageInfo2.versionName, apps[1].installedVersionName)
- }
-
- // get all apps in repo2 searching for summary
- appDao.getAppListItems(pm, repoId2, "Zfassung", NAME).getOrFail().let { apps ->
- assertEquals(2, apps.size)
- val sortedApps = apps.sortedBy { it.lastUpdated }
- assertEquals(app2, sortedApps[0])
- assertEquals(app3a, sortedApps[1])
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo2),
- sortedApps[0].installedVersionCode
- )
- assertEquals(packageInfo2.versionName, sortedApps[0].installedVersionName)
- }
-
- // get first app by searching for summary
- appDao.getAppListItems(pm, repoId1, "One", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // get third app by searching for summary in repo3 only
- appDao.getAppListItems(pm, repoId3, "summary", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app3b, apps[0])
- }
-
- // empty search for unknown category
- appDao.getAppListItems(pm, 1337L, "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
-
- // empty search for unknown search term
- appDao.getAppListItems(pm, repoId2, "foo bar", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
+ // get second app by search, sort order doesn't matter
+ appDao.getAppListItems(pm, "Two", NAME).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2, apps[0])
+ assertEquals(PackageInfoCompat.getLongVersionCode(packageInfo2), apps[0].installedVersionCode)
+ assertEquals(packageInfo2.versionName, apps[0].installedVersionName)
}
- @Test
- fun testMalformedSearchQuery() {
- every { pm.getInstalledPackages(0) } returns emptyList()
-
- // without category
- appDao.getAppListItems(pm, "\"", LAST_UPDATED).getOrFail().let { apps ->
- assertTrue(apps.isEmpty())
- }
- appDao.getAppListItems(pm, "*simple\"*", NAME).getOrFail().let { apps ->
- assertTrue(apps.isEmpty())
- }
-
- // with category
- appDao.getAppListItems(pm, "Category", "\"", LAST_UPDATED).getOrFail().let { apps ->
- assertTrue(apps.isEmpty())
- }
- appDao.getAppListItems(pm, "Category", "*simple\"*", NAME).getOrFail().let { apps ->
- assertTrue(apps.isEmpty())
- }
+ // get second and third app by searching for summary
+ appDao.getAppListItems(pm, "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ // sort-order isn't fixes, yet
+ if (apps[0].packageName == packageName2) {
+ assertEquals(app2, apps[0])
+ assertEquals(app3, apps[1])
+ } else {
+ assertEquals(app3, apps[0])
+ assertEquals(app2, apps[1])
+ }
}
- @Test
- fun testSortOrderByLastUpdated() {
- // insert three apps in a random order
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName3, app3, locales)
- appDao.insert(repoId, packageName2, app2, locales)
+ // empty search for unknown search term
+ appDao.getAppListItems(pm, "foo bar", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(0, apps.size)
+ }
+ }
- // nothing is installed
- every { pm.getInstalledPackages(0) } returns emptyList()
+ @Test
+ fun testSearchQueryInCategory() {
+ val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
+ val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
+ val app3 = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
+ // insert three apps in a random order
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
- // get apps sorted by last updated
- appDao.getAppListItems(pm, "", LAST_UPDATED).getOrFail().let { apps ->
- assertEquals(3, apps.size)
- // we expect apps to be sorted by last updated descending
- appPairs.sortedByDescending { (_, metadataV2) ->
- metadataV2.lastUpdated
- }.forEachIndexed { i, pair ->
- assertEquals(pair.first, apps[i].packageName)
- assertEquals(pair.second, apps[i])
- }
- }
+ // one of the apps is installed
+ val packageInfo2 =
+ PackageInfo().apply {
+ packageName = packageName2
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
+
+ // get first app by search, sort order doesn't matter
+ appDao.getAppListItems(pm, "A", "One", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testSortOrderByName() {
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
-
- // nothing is installed
- every { pm.getInstalledPackages(0) } returns emptyList()
-
- // get apps sorted by name ascending
- appDao.getAppListItems(pm, null, NAME).getOrFail().let { apps ->
- assertEquals(3, apps.size)
- // we expect apps to be sorted by last updated descending
- appPairs.sortedBy { (_, metadataV2) ->
- metadataV2.name.getBestLocale(locales)
- }.forEachIndexed { i, pair ->
- assertEquals(pair.first, apps[i].packageName)
- assertEquals(pair.second, apps[i])
- }
- }
+ // get second app by search, sort order doesn't matter
+ appDao.getAppListItems(pm, "A", "Two", NAME).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2, apps[0])
+ assertEquals(PackageInfoCompat.getLongVersionCode(packageInfo2), apps[0].installedVersionCode)
+ assertEquals(packageInfo2.versionName, apps[0].installedVersionName)
}
- @Test
- fun testPackageManagerInfo() {
- // insert two apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
-
- // one of the apps is installed
- val packageInfo2 = PackageInfo().apply {
- packageName = packageName2
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
-
- // get apps sorted by name and last update, test on both lists
- listOf(
- appDao.getAppListItems(pm, "", NAME).getOrFail(),
- appDao.getAppListItems(pm, null, LAST_UPDATED).getOrFail(),
- ).forEach { apps ->
- assertEquals(2, apps.size)
- // the installed app should have app data
- val installed = if (apps[0].packageName == packageName1) apps[1] else apps[0]
- val other = if (apps[0].packageName == packageName1) apps[0] else apps[1]
- assertEquals(packageInfo2.versionName, installed.installedVersionName)
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo2),
- installed.installedVersionCode
- )
- assertNull(other.installedVersionName)
- assertNull(other.installedVersionCode)
- }
+ // get second and third app by searching for summary
+ appDao.getAppListItems(pm, "A", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ // sort-order isn't fixes, yet
+ if (apps[0].packageName == packageName2) {
+ assertEquals(app2, apps[0])
+ assertEquals(app3, apps[1])
+ } else {
+ assertEquals(app3, apps[0])
+ assertEquals(app2, apps[1])
+ }
}
- @Test
- fun testCompatibility() {
- // insert two apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
-
- // both apps are not compatible
- getItems { apps ->
- assertEquals(2, apps.size)
- assertFalse(apps[0].isCompatible)
- assertFalse(apps[1].isCompatible)
- }
-
- // each app gets a version
- versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
- versionDao.insert(repoId, packageName2, "1", getRandomPackageVersionV2(), false)
-
- // updating compatibility for apps
- appDao.updateCompatibility(repoId)
-
- // now only one is not compatible
- getItems { apps ->
- assertEquals(2, apps.size)
- if (apps[0].packageName == packageName1) {
- assertTrue(apps[0].isCompatible)
- assertFalse(apps[1].isCompatible)
- } else {
- assertFalse(apps[0].isCompatible)
- assertTrue(apps[1].isCompatible)
- }
- }
+ // get third app by searching for summary in category B only
+ appDao.getAppListItems(pm, "B", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app3, apps[0])
}
- @Test
- fun testAntiFeaturesFromHighestVersion() {
- // insert one app with no versions
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
-
- // app has no anti-features, because no version
- getItems { apps ->
- assertEquals(1, apps.size)
- assertNull(apps[0].antiFeatures)
- assertEquals(emptyList(), apps[0].antiFeatureKeys)
- }
-
- // app gets a version
- val version1 = getRandomPackageVersionV2(42)
- versionDao.insert(repoId, packageName1, "1", version1, true)
-
- // app has now has the anti-features of the version
- // note that installed versions don't contain anti-features, so they are ignored
- getItems(alsoInstalled = false) { apps ->
- assertEquals(1, apps.size)
- assertEquals(version1.antiFeatures.map { it.key }, apps[0].antiFeatureKeys)
- }
-
- // app gets another version
- val version2 = getRandomPackageVersionV2(23)
- versionDao.insert(repoId, packageName1, "2", version2, true)
-
- // app has now has the anti-features of the initial version still, because 2nd is lower
- // note that installed versions don't contain anti-features, so they are ignored
- getItems(alsoInstalled = false) { apps ->
- assertEquals(1, apps.size)
- assertEquals(version1.antiFeatures.map { it.key }, apps[0].antiFeatureKeys)
- }
+ // empty search for unknown category
+ appDao.getAppListItems(pm, "C", "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(0, apps.size)
}
- @Test
- fun testOnlyFromEnabledRepos() {
- // insert two apps in two different repos
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId2, packageName2, app2, locales)
+ // empty search for unknown search term
+ appDao.getAppListItems(pm, "A", "foo bar", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(0, apps.size)
+ }
+ }
- // initially both apps get returned
- getItems { apps ->
- assertEquals(2, apps.size)
- }
+ @Test
+ fun testSearchQueryPerRepo() {
+ val app1 = app1.copy(name = mapOf("en-US" to "One"), summary = mapOf("en-US" to "Onearry"))
+ val app2 = app2.copy(name = mapOf("en-US" to "Two"), summary = mapOf("de" to "Zfassung"))
+ val app3a = app3.copy(name = mapOf("de-DE" to "Drei"), summary = mapOf("de" to "Zfassung"))
+ val app3b = app3.copy(name = mapOf("en-US" to "Three"), summary = mapOf("en" to "summary"))
+ // insert three apps in a random order
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName1, app1, locales)
+ appDao.insert(repoId2, packageName2, app2, locales)
+ appDao.insert(repoId2, packageName3, app3a, locales)
+ appDao.insert(repoId3, packageName3, app3b, locales)
- // disable first repo
- repoDao.setRepositoryEnabled(repoId, false)
+ // one of the apps is installed
+ val packageInfo2 =
+ PackageInfo().apply {
+ packageName = packageName2
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
- // now only app from enabled repo gets returned
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(repoId2, apps[0].repoId)
- }
+ // get all apps in repo2 sorted by last updated
+ appDao.getAppListItems(pm, repoId2, null, LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ assertEquals(app3a, apps[0])
+ assertEquals(app2, apps[1])
+ assertEquals(PackageInfoCompat.getLongVersionCode(packageInfo2), apps[1].installedVersionCode)
+ assertEquals(packageInfo2.versionName, apps[1].installedVersionName)
}
- @Test
- fun testFromRepoWithHighestWeight() {
- // insert same app into three repos (repoId1 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId3, packageName, app3, locales)
-
- // ensure expected repo weights
- val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
- val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
- val repoPrefs3 = repoDao.getRepositoryPreferences(repoId3) ?: fail()
- assertTrue(repoPrefs2.weight < repoPrefs3.weight)
- assertTrue(repoPrefs3.weight < repoPrefs1.weight)
-
- // app from repo with highest weight is returned (app1)
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- assertEquals(app1, apps[0])
- }
+ // get all apps in repo2 searching for summary
+ appDao.getAppListItems(pm, repoId2, "Zfassung", NAME).getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ val sortedApps = apps.sortedBy { it.lastUpdated }
+ assertEquals(app2, sortedApps[0])
+ assertEquals(app3a, sortedApps[1])
+ assertEquals(
+ PackageInfoCompat.getLongVersionCode(packageInfo2),
+ sortedApps[0].installedVersionCode,
+ )
+ assertEquals(packageInfo2.versionName, sortedApps[0].installedVersionName)
}
- @Test
- fun testFromRepoFromAppPrefs() {
- // insert same app into three repos (repoId1 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId3, packageName, app3, locales)
-
- // app from repo1 with highest weight gets returned
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- assertEquals(app1, apps[0])
- }
-
- // prefer repo3 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- assertEquals(app3, apps[0])
- }
-
- // prefer repo2 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- assertEquals(app2, apps[0])
- }
-
- // prefer repo1 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
- getItems { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- assertEquals(app1, apps[0])
- }
+ // get first app by searching for summary
+ appDao.getAppListItems(pm, repoId1, "One", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testOnlyFromGivenCategories() {
- // nothing is installed
- every { pm.getInstalledPackages(0) } returns emptyList()
-
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
-
- // only two apps are in category B
- listOf(
- appDao.getAppListItems(pm, "B", null, NAME).getOrFail(),
- appDao.getAppListItems(pm, "B", null, LAST_UPDATED).getOrFail(),
- ).forEach { apps ->
- assertEquals(2, apps.size)
- assertNotEquals(packageName2, apps[0].packageName)
- assertNotEquals(packageName2, apps[1].packageName)
- }
-
- // no app is in category C
- listOf(
- appDao.getAppListItems(pm, "C", null, NAME).getOrFail(),
- appDao.getAppListItems(pm, "C", null, LAST_UPDATED).getOrFail(),
- ).forEach { apps ->
- assertEquals(0, apps.size)
- }
-
- // we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName2, app1, locales)
- listOf(
- appDao.getAppListItems(pm, "B", null, NAME).getOrFail(),
- appDao.getAppListItems(pm, "B", null, LAST_UPDATED).getOrFail(),
- ).forEach { apps ->
- assertEquals(2, apps.size)
- assertNotEquals(packageName2, apps[0].packageName)
- assertNotEquals(packageName2, apps[1].packageName)
- }
+ // get third app by searching for summary in repo3 only
+ appDao.getAppListItems(pm, repoId3, "summary", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app3b, apps[0])
}
- @Test
- fun testGetInstalledAppListItems() {
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
-
- // define packageInfo for each test
- val packageInfo1 = PackageInfo().apply {
- packageName = packageName1
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
- val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
-
- // all apps get returned, if we consider all of them installed
- every {
- pm.getInstalledPackages(0)
- } returns listOf(packageInfo1, packageInfo2, packageInfo3)
- assertEquals(3, appDao.getInstalledAppListItems(pm).getOrFail().size)
-
- // one apps get returned, if we consider only that one installed
- every { pm.getInstalledPackages(0) } returns listOf(packageInfo1)
- appDao.getInstalledAppListItems(pm).getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- // version code and version name gets taken from supplied packageInfo
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo1),
- apps[0].installedVersionCode
- )
- assertEquals(packageInfo1.versionName, apps[0].installedVersionName)
- }
-
- // no app gets returned, if we consider none installed
- every { pm.getInstalledPackages(0) } returns emptyList()
- appDao.getInstalledAppListItems(pm).getOrFail().let { apps ->
- assertEquals(0, apps.size)
- }
+ // empty search for unknown category
+ appDao.getAppListItems(pm, 1337L, "Zfassung", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(0, apps.size)
}
- @Test
- fun testGetInstalledAppListItemsFlow() = runBlocking {
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
+ // empty search for unknown search term
+ appDao.getAppListItems(pm, repoId2, "foo bar", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(0, apps.size)
+ }
+ }
- // define packageInfo for each test
- val packageInfo1 = PackageInfo().apply {
- packageName = packageName1
- versionName = getRandomString()
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
- val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
+ @Test
+ fun testMalformedSearchQuery() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
- // all apps get returned, if we consider all of them installed
- val pmInfoMap1 = mapOf(
- packageName1 to packageInfo1,
- packageName2 to packageInfo2,
- packageName3 to packageInfo3,
- )
- assertEquals(3, appDao.getInstalledAppListItems(pmInfoMap1).first().size)
-
- // one apps get returned, if we consider only that one installed
- val pmInfoMap2 = mapOf(packageName1 to packageInfo1)
- appDao.getInstalledAppListItems(pmInfoMap2).first().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- // version code and version name gets taken from supplied packageInfo
- assertEquals(
- PackageInfoCompat.getLongVersionCode(packageInfo1),
- apps[0].installedVersionCode
- )
- assertEquals(packageInfo1.versionName, apps[0].installedVersionName)
- }
-
- // no app gets returned, if we consider none installed
- appDao.getInstalledAppListItems(emptyMap()).first().let { apps ->
- assertEquals(0, apps.size)
- }
+ // without category
+ appDao.getAppListItems(pm, "\"", LAST_UPDATED).getOrFail().let { apps ->
+ assertTrue(apps.isEmpty())
+ }
+ appDao.getAppListItems(pm, "*simple\"*", NAME).getOrFail().let { apps ->
+ assertTrue(apps.isEmpty())
}
- @Test
- fun testGetInstalledAppListItemsMaxVars() {
- // insert an app
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
-
- val packageInfoCreator = { name: String ->
- PackageInfo().apply {
- packageName = name
- versionName = name
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- }
- val packageInfo = packageInfoCreator(packageName)
-
- // sqlite (before 3.32.0) has a maximum number of 999 variables that can be used in a query
- // one additional package info is added to the package lists with each test case
- val listPackageInfo = listOf(packageInfo)
- val packageInfoOk = MutableList(998) { packageInfoCreator("${it + 1}") }
- val packageInfoNotOk1 = MutableList(999) { packageInfoCreator("${it + 1}") }
- val packageInfoNotOk2 = MutableList(5000) { packageInfoCreator("${it + 1}") }
-
- // app gets returned no matter how many packages are installed
- every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
- assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
- every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
- assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
- every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
- assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
-
- // ensure they have version info set
- every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
- assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
- every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
- assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
- every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
- assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
+ // with category
+ appDao.getAppListItems(pm, "Category", "\"", LAST_UPDATED).getOrFail().let { apps ->
+ assertTrue(apps.isEmpty())
}
-
- @Test
- fun testGetInstalledAppListItemsFlowMaxVars(): Unit = runBlocking {
- // insert an app
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
-
- val packageInfoCreator = { name: String ->
- PackageInfo().apply {
- packageName = name
- versionName = name
- versionCode = Random.nextInt(1, Int.MAX_VALUE)
- }
- }
- val packageInfo = packageInfoCreator(packageName)
- // sqlite (before 3.32.0) has a maximum number of 999 variables that can be used in a query
- // one additional package info is added to the package lists with each test case
- val packageInfoOk = mutableMapOf().apply {
- for (i in 2..999) set("$i", packageInfoCreator("$i"))
- set(packageName, packageInfo)
- }
- val packageInfoNotOk1 = mutableMapOf().apply {
- for (i in 2..1000) set("$i", packageInfoCreator("$i"))
- set(packageName, packageInfo)
- }
- val packageInfoNotOk2 = mutableMapOf().apply {
- for (i in 2..5000) set("$i", packageInfoCreator("$i"))
- set(packageName, packageInfo)
- }
- // app gets returned no matter how many packages are installed
- assertEquals(1, appDao.getInstalledAppListItems(packageInfoOk).first().size)
- assertEquals(1, appDao.getInstalledAppListItems(packageInfoNotOk1).first().size)
- assertEquals(1, appDao.getInstalledAppListItems(packageInfoNotOk2).first().size)
-
- // ensure they have version info set
- assertNotNull(
- appDao.getInstalledAppListItems(packageInfoOk).first()[0].installedVersionName
- )
- assertNotNull(
- appDao.getInstalledAppListItems(packageInfoNotOk1).first()[0].installedVersionName
- )
- assertNotNull(
- appDao.getInstalledAppListItems(packageInfoNotOk2).first()[0].installedVersionName
- )
+ appDao.getAppListItems(pm, "Category", "*simple\"*", NAME).getOrFail().let { apps ->
+ assertTrue(apps.isEmpty())
}
+ }
- // region author tests
- @Test
- fun testAuthor_NoApp() {
- // should never happen, but test this just in case
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
+ @Test
+ fun testSortOrderByLastUpdated() {
+ // insert three apps in a random order
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
- assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- assertTrue(appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail().isEmpty())
- assertTrue(
- appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED)
- .getOrFail().isEmpty()
- )
+ // nothing is installed
+ every { pm.getInstalledPackages(0) } returns emptyList()
+
+ // get apps sorted by last updated
+ appDao.getAppListItems(pm, "", LAST_UPDATED).getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ // we expect apps to be sorted by last updated descending
+ appPairs
+ .sortedByDescending { (_, metadataV2) -> metadataV2.lastUpdated }
+ .forEachIndexed { i, pair ->
+ assertEquals(pair.first, apps[i].packageName)
+ assertEquals(pair.second, apps[i])
+ }
}
+ }
- @Test
- fun testAuthor_OneApp() {
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
- val packageName = getRandomString()
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
+ @Test
+ fun testSortOrderByName() {
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
- assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
- assertEquals(1, appsForAuthor.size)
+ // nothing is installed
+ every { pm.getInstalledPackages(0) } returns emptyList()
+
+ // get apps sorted by name ascending
+ appDao.getAppListItems(pm, null, NAME).getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ // we expect apps to be sorted by last updated descending
+ appPairs
+ .sortedBy { (_, metadataV2) -> metadataV2.name.getBestLocale(locales) }
+ .forEachIndexed { i, pair ->
+ assertEquals(pair.first, apps[i].packageName)
+ assertEquals(pair.second, apps[i])
+ }
+ }
+ }
+
+ @Test
+ fun testPackageManagerInfo() {
+ // insert two apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+
+ // one of the apps is installed
+ val packageInfo2 =
+ PackageInfo().apply {
+ packageName = packageName2
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo2)
+
+ // get apps sorted by name and last update, test on both lists
+ listOf(
+ appDao.getAppListItems(pm, "", NAME).getOrFail(),
+ appDao.getAppListItems(pm, null, LAST_UPDATED).getOrFail(),
+ )
+ .forEach { apps ->
+ assertEquals(2, apps.size)
+ // the installed app should have app data
+ val installed = if (apps[0].packageName == packageName1) apps[1] else apps[0]
+ val other = if (apps[0].packageName == packageName1) apps[0] else apps[1]
+ assertEquals(packageInfo2.versionName, installed.installedVersionName)
assertEquals(
- 1, appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED)
- .getOrFail().size
+ PackageInfoCompat.getLongVersionCode(packageInfo2),
+ installed.installedVersionCode,
)
- assertEquals(packageName, appsForAuthor[0].packageName)
+ assertNull(other.installedVersionName)
+ assertNull(other.installedVersionCode)
+ }
+ }
+
+ @Test
+ fun testCompatibility() {
+ // insert two apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+
+ // both apps are not compatible
+ getItems { apps ->
+ assertEquals(2, apps.size)
+ assertFalse(apps[0].isCompatible)
+ assertFalse(apps[1].isCompatible)
}
- @Test
- fun testAuthor_TwoApps() {
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- val packageName1 = getRandomString()
- val packageName2 = getRandomString()
- val metadata2 = getRandomMetadataV2(author)
- val metadata1 = getRandomMetadataV2(author, metadata2.lastUpdated + 1)
- appDao.insert(repoId, packageName1, metadata1, locales)
- appDao.insert(repoId, packageName2, metadata2, locales)
+ // each app gets a version
+ versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
+ versionDao.insert(repoId, packageName2, "1", getRandomPackageVersionV2(), false)
- assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED)
- .getOrFail()
- assertEquals(2, appsForAuthor.size)
- assertEquals(packageName1, appsForAuthor[0].packageName)
- assertEquals(packageName2, appsForAuthor[1].packageName)
+ // updating compatibility for apps
+ appDao.updateCompatibility(repoId)
+
+ // now only one is not compatible
+ getItems { apps ->
+ assertEquals(2, apps.size)
+ if (apps[0].packageName == packageName1) {
+ assertTrue(apps[0].isCompatible)
+ assertFalse(apps[1].isCompatible)
+ } else {
+ assertFalse(apps[0].isCompatible)
+ assertTrue(apps[1].isCompatible)
+ }
+ }
+ }
+
+ @Test
+ fun testAntiFeaturesFromHighestVersion() {
+ // insert one app with no versions
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+
+ // app has no anti-features, because no version
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertNull(apps[0].antiFeatures)
+ assertEquals(emptyList(), apps[0].antiFeatureKeys)
}
- @Test
- fun testAuthor_MultipleApps() {
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- for (i in 1..50) {
- appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
- }
+ // app gets a version
+ val version1 = getRandomPackageVersionV2(42)
+ versionDao.insert(repoId, packageName1, "1", version1, true)
- assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- assertEquals(50, appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail().size)
+ // app has now has the anti-features of the version
+ // note that installed versions don't contain anti-features, so they are ignored
+ getItems(alsoInstalled = false) { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(version1.antiFeatures.map { it.key }, apps[0].antiFeatureKeys)
}
- @Test
- fun testHasAuthorMoreThanOneApp_OneAppDifferentRepos() {
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val packageName = getRandomString()
- appDao.insert(repoId1, packageName, getRandomMetadataV2(author), locales)
- val app4 = getRandomMetadataV2(author)
- appDao.insert(repoId2, packageName, app4, locales)
- appPrefsDao.update(
- AppPrefs(
- packageName = packageName,
- preferredRepoId = repoId2,
- )
- )
- assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
- assertEquals(1, appsForAuthor.size)
- assertEquals(packageName, appsForAuthor[0].packageName)
- assertEquals(repoId2, appsForAuthor[0].repoId)
+ // app gets another version
+ val version2 = getRandomPackageVersionV2(23)
+ versionDao.insert(repoId, packageName1, "2", version2, true)
+
+ // app has now has the anti-features of the initial version still, because 2nd is lower
+ // note that installed versions don't contain anti-features, so they are ignored
+ getItems(alsoInstalled = false) { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(version1.antiFeatures.map { it.key }, apps[0].antiFeatureKeys)
+ }
+ }
+
+ @Test
+ fun testOnlyFromEnabledRepos() {
+ // insert two apps in two different repos
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId2, packageName2, app2, locales)
+
+ // initially both apps get returned
+ getItems { apps -> assertEquals(2, apps.size) }
+
+ // disable first repo
+ repoDao.setRepositoryEnabled(repoId, false)
+
+ // now only app from enabled repo gets returned
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(repoId2, apps[0].repoId)
+ }
+ }
+
+ @Test
+ fun testFromRepoWithHighestWeight() {
+ // insert same app into three repos (repoId1 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId3, packageName, app3, locales)
+
+ // ensure expected repo weights
+ val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
+ val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
+ val repoPrefs3 = repoDao.getRepositoryPreferences(repoId3) ?: fail()
+ assertTrue(repoPrefs2.weight < repoPrefs3.weight)
+ assertTrue(repoPrefs3.weight < repoPrefs1.weight)
+
+ // app from repo with highest weight is returned (app1)
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ assertEquals(app1, apps[0])
+ }
+ }
+
+ @Test
+ fun testFromRepoFromAppPrefs() {
+ // insert same app into three repos (repoId1 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId3, packageName, app3, locales)
+
+ // app from repo1 with highest weight gets returned
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testHasAuthorMoreThanOneApp_TwoAppsDifferentRepos() {
- every { pm.getInstalledPackages(0) } returns emptyList()
- val author = getRandomString()
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, getRandomString(), getRandomMetadataV2(author), locales)
- appDao.insert(repoId2, getRandomString(), getRandomMetadataV2(author), locales)
-
- assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
- assertEquals(2, appsForAuthor.size)
- }
- // endregion
-
- /**
- * Runs the given block on all getAppListItems* methods.
- * Uses category "A" as all apps should be in that.
- */
- private fun getItems(alsoInstalled: Boolean = true, block: (List) -> Unit) {
- every { pm.getInstalledPackages(0) } returns emptyList()
- appDao.getAppListItemsByName().getOrFail().let(block)
- appDao.getAppListItems(pm, "A", null, NAME).getOrFail().let(block)
- appDao.getAppListItemsByLastUpdated().getOrFail().let(block)
- appDao.getAppListItems(pm, "A", null, LAST_UPDATED).getOrFail().let(block)
- if (alsoInstalled) {
- // everything is always considered to be installed
- val packageInfo =
- PackageInfo().apply { packageName = this@AppListItemsTest.packageName }
- val packageInfo1 = PackageInfo().apply { packageName = packageName1 }
- val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
- val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
- every {
- pm.getInstalledPackages(0)
- } returns listOf(packageInfo, packageInfo1, packageInfo2, packageInfo3)
- appDao.getInstalledAppListItems(pm).getOrFail().let(block)
- }
+ // prefer repo3 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ assertEquals(app3, apps[0])
}
- private fun assertEquals(expected: MetadataV2, actual: AppListItem) {
- assertEquals(expected.name.getBestLocale(locales), actual.name)
- assertEquals(expected.summary.getBestLocale(locales), actual.summary)
- assertEquals(expected.icon.getBestLocale(locales), actual.getIcon(locales))
+ // prefer repo2 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ assertEquals(app2, apps[0])
}
+ // prefer repo1 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
+ getItems { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ assertEquals(app1, apps[0])
+ }
+ }
+
+ @Test
+ fun testOnlyFromGivenCategories() {
+ // nothing is installed
+ every { pm.getInstalledPackages(0) } returns emptyList()
+
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+
+ // only two apps are in category B
+ listOf(
+ appDao.getAppListItems(pm, "B", null, NAME).getOrFail(),
+ appDao.getAppListItems(pm, "B", null, LAST_UPDATED).getOrFail(),
+ )
+ .forEach { apps ->
+ assertEquals(2, apps.size)
+ assertNotEquals(packageName2, apps[0].packageName)
+ assertNotEquals(packageName2, apps[1].packageName)
+ }
+
+ // no app is in category C
+ listOf(
+ appDao.getAppListItems(pm, "C", null, NAME).getOrFail(),
+ appDao.getAppListItems(pm, "C", null, LAST_UPDATED).getOrFail(),
+ )
+ .forEach { apps -> assertEquals(0, apps.size) }
+
+ // we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName2, app1, locales)
+ listOf(
+ appDao.getAppListItems(pm, "B", null, NAME).getOrFail(),
+ appDao.getAppListItems(pm, "B", null, LAST_UPDATED).getOrFail(),
+ )
+ .forEach { apps ->
+ assertEquals(2, apps.size)
+ assertNotEquals(packageName2, apps[0].packageName)
+ assertNotEquals(packageName2, apps[1].packageName)
+ }
+ }
+
+ @Test
+ fun testGetInstalledAppListItems() {
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+
+ // define packageInfo for each test
+ val packageInfo1 =
+ PackageInfo().apply {
+ packageName = packageName1
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
+ val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
+
+ // all apps get returned, if we consider all of them installed
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo1, packageInfo2, packageInfo3)
+ assertEquals(3, appDao.getInstalledAppListItems(pm).getOrFail().size)
+
+ // one apps get returned, if we consider only that one installed
+ every { pm.getInstalledPackages(0) } returns listOf(packageInfo1)
+ appDao.getInstalledAppListItems(pm).getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
+ // version code and version name gets taken from supplied packageInfo
+ assertEquals(PackageInfoCompat.getLongVersionCode(packageInfo1), apps[0].installedVersionCode)
+ assertEquals(packageInfo1.versionName, apps[0].installedVersionName)
+ }
+
+ // no app gets returned, if we consider none installed
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ appDao.getInstalledAppListItems(pm).getOrFail().let { apps -> assertEquals(0, apps.size) }
+ }
+
+ @Test
+ fun testGetInstalledAppListItemsFlow() = runBlocking {
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+
+ // define packageInfo for each test
+ val packageInfo1 =
+ PackageInfo().apply {
+ packageName = packageName1
+ versionName = getRandomString()
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
+ val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
+
+ // all apps get returned, if we consider all of them installed
+ val pmInfoMap1 =
+ mapOf(
+ packageName1 to packageInfo1,
+ packageName2 to packageInfo2,
+ packageName3 to packageInfo3,
+ )
+ assertEquals(3, appDao.getInstalledAppListItems(pmInfoMap1).first().size)
+
+ // one apps get returned, if we consider only that one installed
+ val pmInfoMap2 = mapOf(packageName1 to packageInfo1)
+ appDao.getInstalledAppListItems(pmInfoMap2).first().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
+ // version code and version name gets taken from supplied packageInfo
+ assertEquals(PackageInfoCompat.getLongVersionCode(packageInfo1), apps[0].installedVersionCode)
+ assertEquals(packageInfo1.versionName, apps[0].installedVersionName)
+ }
+
+ // no app gets returned, if we consider none installed
+ appDao.getInstalledAppListItems(emptyMap()).first().let { apps -> assertEquals(0, apps.size) }
+ }
+
+ @Test
+ fun testGetInstalledAppListItemsMaxVars() {
+ // insert an app
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
+
+ val packageInfoCreator = { name: String ->
+ PackageInfo().apply {
+ packageName = name
+ versionName = name
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ }
+ val packageInfo = packageInfoCreator(packageName)
+
+ // sqlite (before 3.32.0) has a maximum number of 999 variables that can be used in a query
+ // one additional package info is added to the package lists with each test case
+ val listPackageInfo = listOf(packageInfo)
+ val packageInfoOk = MutableList(998) { packageInfoCreator("${it + 1}") }
+ val packageInfoNotOk1 = MutableList(999) { packageInfoCreator("${it + 1}") }
+ val packageInfoNotOk2 = MutableList(5000) { packageInfoCreator("${it + 1}") }
+
+ // app gets returned no matter how many packages are installed
+ every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
+ assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
+ every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
+ assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
+ every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
+ assertEquals(1, appDao.getInstalledAppListItems(pm).getOrFail().size)
+
+ // ensure they have version info set
+ every { pm.getInstalledPackages(0) } returns packageInfoOk + listPackageInfo
+ assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
+ every { pm.getInstalledPackages(0) } returns packageInfoNotOk1 + listPackageInfo
+ assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
+ every { pm.getInstalledPackages(0) } returns packageInfoNotOk2 + listPackageInfo
+ assertNotNull(appDao.getInstalledAppListItems(pm).getOrFail()[0].installedVersionName)
+ }
+
+ @Test
+ fun testGetInstalledAppListItemsFlowMaxVars(): Unit = runBlocking {
+ // insert an app
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
+
+ val packageInfoCreator = { name: String ->
+ PackageInfo().apply {
+ packageName = name
+ versionName = name
+ versionCode = Random.nextInt(1, Int.MAX_VALUE)
+ }
+ }
+ val packageInfo = packageInfoCreator(packageName)
+ // sqlite (before 3.32.0) has a maximum number of 999 variables that can be used in a query
+ // one additional package info is added to the package lists with each test case
+ val packageInfoOk =
+ mutableMapOf().apply {
+ for (i in 2..999) set("$i", packageInfoCreator("$i"))
+ set(packageName, packageInfo)
+ }
+ val packageInfoNotOk1 =
+ mutableMapOf().apply {
+ for (i in 2..1000) set("$i", packageInfoCreator("$i"))
+ set(packageName, packageInfo)
+ }
+ val packageInfoNotOk2 =
+ mutableMapOf().apply {
+ for (i in 2..5000) set("$i", packageInfoCreator("$i"))
+ set(packageName, packageInfo)
+ }
+ // app gets returned no matter how many packages are installed
+ assertEquals(1, appDao.getInstalledAppListItems(packageInfoOk).first().size)
+ assertEquals(1, appDao.getInstalledAppListItems(packageInfoNotOk1).first().size)
+ assertEquals(1, appDao.getInstalledAppListItems(packageInfoNotOk2).first().size)
+
+ // ensure they have version info set
+ assertNotNull(appDao.getInstalledAppListItems(packageInfoOk).first()[0].installedVersionName)
+ assertNotNull(
+ appDao.getInstalledAppListItems(packageInfoNotOk1).first()[0].installedVersionName
+ )
+ assertNotNull(
+ appDao.getInstalledAppListItems(packageInfoNotOk2).first()[0].installedVersionName
+ )
+ }
+
+ // region author tests
+ @Test
+ fun testAuthor_NoApp() {
+ // should never happen, but test this just in case
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+
+ assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ assertTrue(appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail().isEmpty())
+ assertTrue(
+ appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED).getOrFail().isEmpty()
+ )
+ }
+
+ @Test
+ fun testAuthor_OneApp() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+ val packageName = getRandomString()
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
+
+ assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
+ assertEquals(1, appsForAuthor.size)
+ assertEquals(
+ 1,
+ appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED).getOrFail().size,
+ )
+ assertEquals(packageName, appsForAuthor[0].packageName)
+ }
+
+ @Test
+ fun testAuthor_TwoApps() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ val packageName1 = getRandomString()
+ val packageName2 = getRandomString()
+ val metadata2 = getRandomMetadataV2(author)
+ val metadata1 = getRandomMetadataV2(author, metadata2.lastUpdated + 1)
+ appDao.insert(repoId, packageName1, metadata1, locales)
+ appDao.insert(repoId, packageName2, metadata2, locales)
+
+ assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, LAST_UPDATED).getOrFail()
+ assertEquals(2, appsForAuthor.size)
+ assertEquals(packageName1, appsForAuthor[0].packageName)
+ assertEquals(packageName2, appsForAuthor[1].packageName)
+ }
+
+ @Test
+ fun testAuthor_MultipleApps() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ for (i in 1..50) {
+ appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
+ }
+
+ assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ assertEquals(50, appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail().size)
+ }
+
+ @Test
+ fun testHasAuthorMoreThanOneApp_OneAppDifferentRepos() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val packageName = getRandomString()
+ appDao.insert(repoId1, packageName, getRandomMetadataV2(author), locales)
+ val app4 = getRandomMetadataV2(author)
+ appDao.insert(repoId2, packageName, app4, locales)
+ appPrefsDao.update(AppPrefs(packageName = packageName, preferredRepoId = repoId2))
+ assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
+ assertEquals(1, appsForAuthor.size)
+ assertEquals(packageName, appsForAuthor[0].packageName)
+ assertEquals(repoId2, appsForAuthor[0].repoId)
+ }
+
+ @Test
+ fun testHasAuthorMoreThanOneApp_TwoAppsDifferentRepos() {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ val author = getRandomString()
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, getRandomString(), getRandomMetadataV2(author), locales)
+ appDao.insert(repoId2, getRandomString(), getRandomMetadataV2(author), locales)
+
+ assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ val appsForAuthor = appDao.getAppListItemsForAuthor(pm, author, null, NAME).getOrFail()
+ assertEquals(2, appsForAuthor.size)
+ }
+
+ // endregion
+
+ /**
+ * Runs the given block on all getAppListItems* methods. Uses category "A" as all apps should be
+ * in that.
+ */
+ private fun getItems(alsoInstalled: Boolean = true, block: (List) -> Unit) {
+ every { pm.getInstalledPackages(0) } returns emptyList()
+ appDao.getAppListItemsByName().getOrFail().let(block)
+ appDao.getAppListItems(pm, "A", null, NAME).getOrFail().let(block)
+ appDao.getAppListItemsByLastUpdated().getOrFail().let(block)
+ appDao.getAppListItems(pm, "A", null, LAST_UPDATED).getOrFail().let(block)
+ if (alsoInstalled) {
+ // everything is always considered to be installed
+ val packageInfo = PackageInfo().apply { packageName = this@AppListItemsTest.packageName }
+ val packageInfo1 = PackageInfo().apply { packageName = packageName1 }
+ val packageInfo2 = PackageInfo().apply { packageName = packageName2 }
+ val packageInfo3 = PackageInfo().apply { packageName = packageName3 }
+ every { pm.getInstalledPackages(0) } returns
+ listOf(packageInfo, packageInfo1, packageInfo2, packageInfo3)
+ appDao.getInstalledAppListItems(pm).getOrFail().let(block)
+ }
+ }
+
+ private fun assertEquals(expected: MetadataV2, actual: AppListItem) {
+ assertEquals(expected.name.getBestLocale(locales), actual.name)
+ assertEquals(expected.summary.getBestLocale(locales), actual.summary)
+ assertEquals(expected.icon.getBestLocale(locales), actual.getIcon(locales))
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppOverviewItemsTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppOverviewItemsTest.kt
index c029272e5..cc4bc01b7 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/AppOverviewItemsTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/AppOverviewItemsTest.kt
@@ -1,6 +1,12 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
import kotlinx.coroutines.runBlocking
import org.fdroid.LocaleChooser.getBestLocale
import org.fdroid.database.TestUtils.getOrAwaitValue
@@ -12,478 +18,463 @@ import org.fdroid.test.TestUtils.getRandomString
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
internal class AppOverviewItemsTest : AppTest() {
- @Test
- fun testAntiFeatures() = runBlocking {
- // insert one app with without version
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
+ @Test
+ fun testAntiFeatures() = runBlocking {
+ // insert one app with without version
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
- // without version, anti-features are empty
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertNull(apps[0].antiFeatures)
- }
-
- // with one version, the app has those anti-features
- val version = getRandomPackageVersionV2(versionCode = 42)
- versionDao.insert(repoId, packageName, "1", version, true)
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(version.antiFeatures, apps[0].antiFeatures)
- }
-
- // with two versions, the app has the anti-features of the highest version
- val version2 = getRandomPackageVersionV2(versionCode = 23)
- versionDao.insert(repoId, packageName, "2", version2, true)
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(version.antiFeatures, apps[0].antiFeatures)
- }
-
- // with three versions, the app has the anti-features of the highest version
- val version3 = getRandomPackageVersionV2(versionCode = 1337)
- versionDao.insert(repoId, packageName, "3", version3, true)
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(version3.antiFeatures, apps[0].antiFeatures)
- }
+ // without version, anti-features are empty
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertNull(apps[0].antiFeatures)
}
- @Test
- fun testIcons() = runBlocking {
- // insert one app
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
-
- // icon is returned correctly
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
- }
-
- // insert same app into another repo
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, app2, locales)
-
- // app is still returned as before
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
- }
-
- // after preferring second repo, icon is returned from app in second repo
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
- }
+ // with one version, the app has those anti-features
+ val version = getRandomPackageVersionV2(versionCode = 42)
+ versionDao.insert(repoId, packageName, "1", version, true)
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(version.antiFeatures, apps[0].antiFeatures)
}
- @Test
- fun testLimit() {
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
-
- // limit is respected
- for (i in 0..3) assertEquals(i, appDao.getAppOverviewItems(i).getOrFail().size)
- assertEquals(3, appDao.getAppOverviewItems(42).getOrFail().size)
+ // with two versions, the app has the anti-features of the highest version
+ val version2 = getRandomPackageVersionV2(versionCode = 23)
+ versionDao.insert(repoId, packageName, "2", version2, true)
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(version.antiFeatures, apps[0].antiFeatures)
}
- @Test
- fun testIncompatibleFlag() = runBlocking {
- // insert two apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
+ // with three versions, the app has the anti-features of the highest version
+ val version3 = getRandomPackageVersionV2(versionCode = 1337)
+ versionDao.insert(repoId, packageName, "3", version3, true)
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(version3.antiFeatures, apps[0].antiFeatures)
+ }
+ }
- // both apps are not compatible
- getItems().forEach { apps ->
- assertEquals(2, apps.size)
- apps.forEach {
- assertFalse(it.isCompatible)
- }
- }
- // both apps, in the same category, are not compatible
- appDao.getAppOverviewItems("A").getOrFail().also {
- assertEquals(2, it.size)
- }.forEach {
- assertFalse(it.isCompatible)
- }
- assertFalse(appDao.getAppOverviewItem(repoId, packageName1)!!.isCompatible)
- assertFalse(appDao.getAppOverviewItem(repoId, packageName2)!!.isCompatible)
+ @Test
+ fun testIcons() = runBlocking {
+ // insert one app
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
- // each app gets a version
- versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
- versionDao.insert(repoId, packageName2, "1", getRandomPackageVersionV2(), false)
-
- // updating compatibility for apps
- appDao.updateCompatibility(repoId)
-
- // now only one is not compatible
- getItems().forEach { apps ->
- assertEquals(2, apps.size)
- assertFalse(apps[0].isCompatible)
- assertTrue(apps[1].isCompatible)
- }
- appDao.getAppOverviewItems("A").getOrFail().also {
- assertEquals(2, it.size)
- assertFalse(it[0].isCompatible)
- assertTrue(it[1].isCompatible)
- }
- assertTrue(appDao.getAppOverviewItem(repoId, packageName1)!!.isCompatible)
- assertFalse(appDao.getAppOverviewItem(repoId, packageName2)!!.isCompatible)
+ // icon is returned correctly
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
}
- @Test
- fun testGetByRepoWeight() = runBlocking {
- // insert one app with one version
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
- versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(2), true)
+ // insert same app into another repo
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, app2, locales)
- // app is returned correctly
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // add another app without version
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, app2, locales)
-
- // app is still returned as before, new repo doesn't override old one
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
-
- // now second app from second repo is returned after preferring it explicitly
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2, apps[0])
- }
+ // app is still returned as before
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1.icon.getBestLocale(locales), apps[0].getIcon(locales))
}
- @Test
- fun testGetByRepoPref() = runBlocking {
- // insert same app into three repos (repoId1 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId3, packageName, app3, locales)
+ // after preferring second repo, icon is returned from app in second repo
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
+ }
+ }
- // app is returned correctly from repo1
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
+ @Test
+ fun testLimit() {
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
- // prefer repo3 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app3, apps[0])
- }
- appDao.getAppOverviewItems("B").getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app3, apps[0])
- }
+ // limit is respected
+ for (i in 0..3) assertEquals(i, appDao.getAppOverviewItems(i).getOrFail().size)
+ assertEquals(3, appDao.getAppOverviewItems(42).getOrFail().size)
+ }
- // prefer repo2 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2, apps[0])
- }
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app2, apps[0])
- }
- appDao.getAppOverviewItems("B").getOrFail().let { apps ->
- assertEquals(0, apps.size) // app2 is not in category B
- }
+ @Test
+ fun testIncompatibleFlag() = runBlocking {
+ // insert two apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
- // prefer repo1 for this app
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(1, apps.size)
- assertEquals(app1, apps[0])
- }
+ // both apps are not compatible
+ getItems().forEach { apps ->
+ assertEquals(2, apps.size)
+ apps.forEach { assertFalse(it.isCompatible) }
+ }
+ // both apps, in the same category, are not compatible
+ appDao
+ .getAppOverviewItems("A")
+ .getOrFail()
+ .also { assertEquals(2, it.size) }
+ .forEach { assertFalse(it.isCompatible) }
+ assertFalse(appDao.getAppOverviewItem(repoId, packageName1)!!.isCompatible)
+ assertFalse(appDao.getAppOverviewItem(repoId, packageName2)!!.isCompatible)
+
+ // each app gets a version
+ versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
+ versionDao.insert(repoId, packageName2, "1", getRandomPackageVersionV2(), false)
+
+ // updating compatibility for apps
+ appDao.updateCompatibility(repoId)
+
+ // now only one is not compatible
+ getItems().forEach { apps ->
+ assertEquals(2, apps.size)
+ assertFalse(apps[0].isCompatible)
+ assertTrue(apps[1].isCompatible)
+ }
+ appDao.getAppOverviewItems("A").getOrFail().also {
+ assertEquals(2, it.size)
+ assertFalse(it[0].isCompatible)
+ assertTrue(it[1].isCompatible)
+ }
+ assertTrue(appDao.getAppOverviewItem(repoId, packageName1)!!.isCompatible)
+ assertFalse(appDao.getAppOverviewItem(repoId, packageName2)!!.isCompatible)
+ }
+
+ @Test
+ fun testGetByRepoWeight() = runBlocking {
+ // insert one app with one version
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
+ versionDao.insert(repoId, packageName, "1", getRandomPackageVersionV2(2), true)
+
+ // app is returned correctly
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testSortOrder() {
- // insert two apps with one version each
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
- versionDao.insert(repoId, packageName2, "2", getRandomPackageVersionV2(), true)
+ // add another app without version
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, app2, locales)
- // icons of both apps are returned correctly
- appDao.getAppOverviewItems().getOrFail().let { apps ->
- assertEquals(2, apps.size)
- // app 2 is first, because has icon and summary
- assertEquals(packageName2, apps[0].packageName)
- assertEquals(icons2, apps[0].localizedIcon?.toLocalizedFileV2())
- // app 1 is next, because has icon
- assertEquals(packageName1, apps[1].packageName)
- assertEquals(icons1, apps[1].localizedIcon?.toLocalizedFileV2())
- }
-
- // app without icon is returned last
- appDao.insert(repoId, packageName3, app3)
- versionDao.insert(repoId, packageName3, "3", getRandomPackageVersionV2(), true)
- appDao.getAppOverviewItems().getOrFail().let { apps ->
- assertEquals(3, apps.size)
- assertEquals(packageName2, apps[0].packageName)
- assertEquals(packageName1, apps[1].packageName)
- assertEquals(packageName3, apps[2].packageName)
- assertEquals(emptyList(), apps[2].localizedIcon)
- }
-
- // app1b is the same as app1 (but in another repo) and thus will not be shown again
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val app1b = app1.copy(name = name2, icon = icons2, summary = name2)
- appDao.insert(repoId2, packageName1, app1b)
- // note that we don't insert a version here
- assertEquals(3, appDao.getAppOverviewItems().getOrFail().size)
-
- // app3b is the same as app3, but has an icon, so is not last anymore
- // after we prefer that repo for this app
- val app3b = app3.copy(icon = icons2)
- appDao.insert(repoId2, packageName3, app3b)
- appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
- // note that we don't insert a version here
- appDao.getAppOverviewItems().getOrFail().let { apps ->
- assertEquals(3, apps.size)
- assertEquals(packageName3, apps[0].packageName)
- assertEquals(emptyList(), apps[0].antiFeatureKeys)
- assertEquals(packageName2, apps[1].packageName)
- assertEquals(packageName1, apps[2].packageName)
- }
+ // app is still returned as before, new repo doesn't override old one
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testSortOrderWithCategories() {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
- versionDao.insert(repoId, packageName2, "2", getRandomPackageVersionV2(), true)
+ // now second app from second repo is returned after preferring it explicitly
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2, apps[0])
+ }
+ }
- // icons of both apps are returned correctly
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(2, apps.size)
- // app 2 is first, because has icon and summary
- assertEquals(packageName2, apps[0].packageName)
- assertEquals(icons2, apps[0].localizedIcon?.toLocalizedFileV2())
- // app 1 is next, because has icon
- assertEquals(packageName1, apps[1].packageName)
- assertEquals(icons1, apps[1].localizedIcon?.toLocalizedFileV2())
- }
+ @Test
+ fun testGetByRepoPref() = runBlocking {
+ // insert same app into three repos (repoId1 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId3, packageName, app3, locales)
- // only one app is returned for category B
- assertEquals(1, appDao.getAppOverviewItems("B").getOrFail().size)
-
- // app without icon is returned last
- appDao.insert(repoId, packageName3, app3)
- versionDao.insert(repoId, packageName3, "3", getRandomPackageVersionV2(), true)
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(3, apps.size)
- assertEquals(packageName2, apps[0].packageName)
- assertEquals(packageName1, apps[1].packageName)
- assertEquals(packageName3, apps[2].packageName)
- assertEquals(emptyList(), apps[2].localizedIcon)
- }
-
- // app1b is the same as app1 (but in another repo) and thus will not be shown again
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val app1b = app1.copy(name = name2, icon = icons2, summary = name2)
- appDao.insert(repoId2, packageName1, app1b)
- // note that we don't insert a version here
- assertEquals(3, appDao.getAppOverviewItems("A").getOrFail().size)
-
- // app3b is the same as app3, but has an icon and is preferred, so is not last anymore
- val app3b = app3.copy(icon = icons2)
- appDao.insert(repoId2, packageName3, app3b)
- appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
- // note that we don't insert a version here
- appDao.getAppOverviewItems("A").getOrFail().let { apps ->
- assertEquals(3, apps.size)
- assertEquals(packageName3, apps[0].packageName)
- assertEquals(emptyList(), apps[0].antiFeatureKeys)
- assertEquals(packageName2, apps[1].packageName)
- assertEquals(packageName1, apps[2].packageName)
- }
-
- // only two apps are returned for category B
- assertEquals(2, appDao.getAppOverviewItems("B").getOrFail().size)
+ // app is returned correctly from repo1
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
+ }
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
}
- @Test
- fun testOnlyFromEnabledRepos() = runBlocking {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName3, app3, locales)
-
- // 3 apps from 2 repos
- getItems().forEach { apps ->
- assertEquals(3, apps.size)
- }
- assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
-
- // only 1 app after disabling first repo
- repoDao.setRepositoryEnabled(repoId, false)
- getItems().forEach { apps ->
- assertEquals(1, apps.size)
- }
- assertEquals(1, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
- assertEquals(1, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
-
- // no more apps after disabling all repos
- repoDao.setRepositoryEnabled(repoId2, false)
- getItems().forEach { apps ->
- assertEquals(0, apps.size)
- }
- assertEquals(0, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
- assertEquals(0, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
+ // prefer repo3 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId3))
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app3, apps[0])
+ }
+ appDao.getAppOverviewItems("B").getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app3, apps[0])
}
- @Test
- fun testGetAppOverviewItem() {
- // insert three apps into two repos
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName3, app3, locales)
-
- // each app gets returned properly
- assertEquals(app1, appDao.getAppOverviewItem(repoId, packageName1))
- assertEquals(app2, appDao.getAppOverviewItem(repoId, packageName2))
- assertEquals(app3, appDao.getAppOverviewItem(repoId2, packageName3))
-
- // apps don't get returned from wrong repos
- assertNull(appDao.getAppOverviewItem(repoId2, packageName1))
- assertNull(appDao.getAppOverviewItem(repoId2, packageName2))
- assertNull(appDao.getAppOverviewItem(repoId, packageName3))
+ // prefer repo2 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId2))
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2, apps[0])
+ }
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app2, apps[0])
+ }
+ appDao.getAppOverviewItems("B").getOrFail().let { apps ->
+ assertEquals(0, apps.size) // app2 is not in category B
}
- @Test
- fun testGetAppOverviewItemWithIcons() {
- // insert one app (with overlapping icons) into two repos
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
+ // prefer repo1 for this app
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
+ getItems().forEach { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
+ }
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(app1, apps[0])
+ }
+ }
- // each app gets returned properly
- assertEquals(app1, appDao.getAppOverviewItem(repoId1, packageName))
- assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
+ @Test
+ fun testSortOrder() {
+ // insert two apps with one version each
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
+ versionDao.insert(repoId, packageName2, "2", getRandomPackageVersionV2(), true)
- // disable second repo
- repoDao.setRepositoryEnabled(repoId2, false)
-
- // each app still gets returned properly
- assertEquals(app1, appDao.getAppOverviewItem(repoId1, packageName))
- assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
+ // icons of both apps are returned correctly
+ appDao.getAppOverviewItems().getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ // app 2 is first, because has icon and summary
+ assertEquals(packageName2, apps[0].packageName)
+ assertEquals(icons2, apps[0].localizedIcon?.toLocalizedFileV2())
+ // app 1 is next, because has icon
+ assertEquals(packageName1, apps[1].packageName)
+ assertEquals(icons1, apps[1].localizedIcon?.toLocalizedFileV2())
}
- @Test
- fun testByAuthor() = runBlocking {
- val author = getRandomString()
- val packageName = getRandomString()
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
-
- // author has only one app
- assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- assertEquals(0, appDao.getAppsByAuthor("foo bar").size)
- appDao.getAppsByAuthor(author).let { apps ->
- assertEquals(1, apps.size)
- assertEquals(packageName, apps[0].packageName)
- }
-
- // now add 49 more apps
- (1 until 50).forEach { _ ->
- appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
- }
- assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
- assertEquals(50, appDao.getAppsByAuthor(author).size)
+ // app without icon is returned last
+ appDao.insert(repoId, packageName3, app3)
+ versionDao.insert(repoId, packageName3, "3", getRandomPackageVersionV2(), true)
+ appDao.getAppOverviewItems().getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ assertEquals(packageName2, apps[0].packageName)
+ assertEquals(packageName1, apps[1].packageName)
+ assertEquals(packageName3, apps[2].packageName)
+ assertEquals(emptyList(), apps[2].localizedIcon)
}
- @Test
- fun testByCategory() = runBlocking {
- // insert three apps
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName1, app1, locales)
- appDao.insert(repoId, packageName2, app2, locales)
- appDao.insert(repoId, packageName3, app3, locales)
+ // app1b is the same as app1 (but in another repo) and thus will not be shown again
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val app1b = app1.copy(name = name2, icon = icons2, summary = name2)
+ appDao.insert(repoId2, packageName1, app1b)
+ // note that we don't insert a version here
+ assertEquals(3, appDao.getAppOverviewItems().getOrFail().size)
- // only two apps are in category B
- appDao.getAppsByCategory("B").let { apps ->
- assertEquals(2, apps.size)
- assertNotEquals(packageName2, apps[0].packageName)
- assertNotEquals(packageName2, apps[1].packageName)
- }
+ // app3b is the same as app3, but has an icon, so is not last anymore
+ // after we prefer that repo for this app
+ val app3b = app3.copy(icon = icons2)
+ appDao.insert(repoId2, packageName3, app3b)
+ appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
+ // note that we don't insert a version here
+ appDao.getAppOverviewItems().getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ assertEquals(packageName3, apps[0].packageName)
+ assertEquals(emptyList(), apps[0].antiFeatureKeys)
+ assertEquals(packageName2, apps[1].packageName)
+ assertEquals(packageName1, apps[2].packageName)
+ }
+ }
- // no app is in category C
- assertEquals(0, appDao.getAppsByCategory("C").size)
+ @Test
+ fun testSortOrderWithCategories() {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ versionDao.insert(repoId, packageName1, "1", getRandomPackageVersionV2(), true)
+ versionDao.insert(repoId, packageName2, "2", getRandomPackageVersionV2(), true)
- // we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName2, app1, locales)
- appDao.getAppsByCategory("B").let { apps ->
- assertEquals(2, apps.size)
- assertNotEquals(packageName2, apps[0].packageName)
- assertNotEquals(packageName2, apps[1].packageName)
- }
+ // icons of both apps are returned correctly
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(2, apps.size)
+ // app 2 is first, because has icon and summary
+ assertEquals(packageName2, apps[0].packageName)
+ assertEquals(icons2, apps[0].localizedIcon?.toLocalizedFileV2())
+ // app 1 is next, because has icon
+ assertEquals(packageName1, apps[1].packageName)
+ assertEquals(icons1, apps[1].localizedIcon?.toLocalizedFileV2())
}
- private suspend fun getItems(): List> {
- return listOf(
- appDao.getAppOverviewItems().getOrFail(),
- // manually sort the second list, so both results are comparable
- appDao.getAllApps().sortedByDescending { it.lastUpdated },
- )
+ // only one app is returned for category B
+ assertEquals(1, appDao.getAppOverviewItems("B").getOrFail().size)
+
+ // app without icon is returned last
+ appDao.insert(repoId, packageName3, app3)
+ versionDao.insert(repoId, packageName3, "3", getRandomPackageVersionV2(), true)
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ assertEquals(packageName2, apps[0].packageName)
+ assertEquals(packageName1, apps[1].packageName)
+ assertEquals(packageName3, apps[2].packageName)
+ assertEquals(emptyList(), apps[2].localizedIcon)
}
- private fun assertEquals(expected: MetadataV2, actual: AppOverviewItem?) {
- assertNotNull(actual)
- assertEquals(expected.added, actual.added)
- assertEquals(expected.lastUpdated, actual.lastUpdated)
- assertEquals(expected.name.getBestLocale(locales), actual.name)
- assertEquals(expected.summary.getBestLocale(locales), actual.summary)
- assertEquals(expected.name.getBestLocale(locales), actual.getName(locales))
- assertEquals(expected.summary.getBestLocale(locales), actual.getSummary(locales))
- assertEquals(expected.icon.getBestLocale(locales), actual.getIcon(locales))
+ // app1b is the same as app1 (but in another repo) and thus will not be shown again
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val app1b = app1.copy(name = name2, icon = icons2, summary = name2)
+ appDao.insert(repoId2, packageName1, app1b)
+ // note that we don't insert a version here
+ assertEquals(3, appDao.getAppOverviewItems("A").getOrFail().size)
+
+ // app3b is the same as app3, but has an icon and is preferred, so is not last anymore
+ val app3b = app3.copy(icon = icons2)
+ appDao.insert(repoId2, packageName3, app3b)
+ appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
+ // note that we don't insert a version here
+ appDao.getAppOverviewItems("A").getOrFail().let { apps ->
+ assertEquals(3, apps.size)
+ assertEquals(packageName3, apps[0].packageName)
+ assertEquals(emptyList(), apps[0].antiFeatureKeys)
+ assertEquals(packageName2, apps[1].packageName)
+ assertEquals(packageName1, apps[2].packageName)
}
+ // only two apps are returned for category B
+ assertEquals(2, appDao.getAppOverviewItems("B").getOrFail().size)
+ }
+
+ @Test
+ fun testOnlyFromEnabledRepos() = runBlocking {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName3, app3, locales)
+
+ // 3 apps from 2 repos
+ getItems().forEach { apps -> assertEquals(3, apps.size) }
+ assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
+
+ // only 1 app after disabling first repo
+ repoDao.setRepositoryEnabled(repoId, false)
+ getItems().forEach { apps -> assertEquals(1, apps.size) }
+ assertEquals(1, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
+ assertEquals(1, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
+
+ // no more apps after disabling all repos
+ repoDao.setRepositoryEnabled(repoId2, false)
+ getItems().forEach { apps -> assertEquals(0, apps.size) }
+ assertEquals(0, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
+ assertEquals(0, appDao.getAppOverviewItems("B").getOrAwaitValue()?.size)
+ }
+
+ @Test
+ fun testGetAppOverviewItem() {
+ // insert three apps into two repos
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName3, app3, locales)
+
+ // each app gets returned properly
+ assertEquals(app1, appDao.getAppOverviewItem(repoId, packageName1))
+ assertEquals(app2, appDao.getAppOverviewItem(repoId, packageName2))
+ assertEquals(app3, appDao.getAppOverviewItem(repoId2, packageName3))
+
+ // apps don't get returned from wrong repos
+ assertNull(appDao.getAppOverviewItem(repoId2, packageName1))
+ assertNull(appDao.getAppOverviewItem(repoId2, packageName2))
+ assertNull(appDao.getAppOverviewItem(repoId, packageName3))
+ }
+
+ @Test
+ fun testGetAppOverviewItemWithIcons() {
+ // insert one app (with overlapping icons) into two repos
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+
+ // each app gets returned properly
+ assertEquals(app1, appDao.getAppOverviewItem(repoId1, packageName))
+ assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
+
+ // disable second repo
+ repoDao.setRepositoryEnabled(repoId2, false)
+
+ // each app still gets returned properly
+ assertEquals(app1, appDao.getAppOverviewItem(repoId1, packageName))
+ assertEquals(app2, appDao.getAppOverviewItem(repoId2, packageName))
+ }
+
+ @Test
+ fun testByAuthor() = runBlocking {
+ val author = getRandomString()
+ val packageName = getRandomString()
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2(author), locales)
+
+ // author has only one app
+ assertFalse(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ assertEquals(0, appDao.getAppsByAuthor("foo bar").size)
+ appDao.getAppsByAuthor(author).let { apps ->
+ assertEquals(1, apps.size)
+ assertEquals(packageName, apps[0].packageName)
+ }
+
+ // now add 49 more apps
+ (1 until 50).forEach { _ ->
+ appDao.insert(repoId, getRandomString(), getRandomMetadataV2(author), locales)
+ }
+ assertTrue(appDao.hasAuthorMoreThanOneApp(author).getOrFail())
+ assertEquals(50, appDao.getAppsByAuthor(author).size)
+ }
+
+ @Test
+ fun testByCategory() = runBlocking {
+ // insert three apps
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName1, app1, locales)
+ appDao.insert(repoId, packageName2, app2, locales)
+ appDao.insert(repoId, packageName3, app3, locales)
+
+ // only two apps are in category B
+ appDao.getAppsByCategory("B").let { apps ->
+ assertEquals(2, apps.size)
+ assertNotEquals(packageName2, apps[0].packageName)
+ assertNotEquals(packageName2, apps[1].packageName)
+ }
+
+ // no app is in category C
+ assertEquals(0, appDao.getAppsByCategory("C").size)
+
+ // we'll add app1 as a variant of app2, but its repo has lower weight, so no effect
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName2, app1, locales)
+ appDao.getAppsByCategory("B").let { apps ->
+ assertEquals(2, apps.size)
+ assertNotEquals(packageName2, apps[0].packageName)
+ assertNotEquals(packageName2, apps[1].packageName)
+ }
+ }
+
+ private suspend fun getItems(): List> {
+ return listOf(
+ appDao.getAppOverviewItems().getOrFail(),
+ // manually sort the second list, so both results are comparable
+ appDao.getAllApps().sortedByDescending { it.lastUpdated },
+ )
+ }
+
+ private fun assertEquals(expected: MetadataV2, actual: AppOverviewItem?) {
+ assertNotNull(actual)
+ assertEquals(expected.added, actual.added)
+ assertEquals(expected.lastUpdated, actual.lastUpdated)
+ assertEquals(expected.name.getBestLocale(locales), actual.name)
+ assertEquals(expected.summary.getBestLocale(locales), actual.summary)
+ assertEquals(expected.name.getBestLocale(locales), actual.getName(locales))
+ assertEquals(expected.summary.getBestLocale(locales), actual.getSummary(locales))
+ assertEquals(expected.icon.getBestLocale(locales), actual.getIcon(locales))
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppPrefsDaoTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppPrefsDaoTest.kt
index 576982f36..0ffc548f3 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/AppPrefsDaoTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/AppPrefsDaoTest.kt
@@ -1,159 +1,158 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.database.TestUtils.toMetadataV2
import org.fdroid.test.TestRepoUtils.getRandomRepo
import org.fdroid.test.TestUtils.sort
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class AppPrefsDaoTest : AppTest() {
- @Test
- fun testDisablingPreferredRepo() {
- // insert same app into three repos (repoId3 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId2, packageName, app3, locales)
+ @Test
+ fun testDisablingPreferredRepo() {
+ // insert same app into three repos (repoId3 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId2, packageName, app3, locales)
- // app from preferred repo gets returned
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // app from preferred repo gets returned
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
- // preferred repo gets disabled
- repoDao.setRepositoryEnabled(repoId1, false)
+ // preferred repo gets disabled
+ repoDao.setRepositoryEnabled(repoId1, false)
- // now app from repo with highest weight is returned
- assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // now app from repo with highest weight is returned
+ assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ }
+
+ @Test
+ fun testRemovingPreferredRepo() {
+ // insert same app into three repos (repoId3 has highest weight)
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+ appDao.insert(repoId2, packageName, app3, locales)
+
+ // app from preferred repo gets returned
+ appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
+ assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+
+ // preferred repo gets removed
+ repoDao.deleteRepository(repoId1)
+
+ // now app from repo with highest weight is returned
+ assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ }
+
+ @Test
+ fun testGetPreferredRepos() {
+ // insert three apps, the third is in repo2 and repo3
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName1, app1, locales)
+ appDao.insert(repoId2, packageName2, app2, locales)
+ appDao.insert(repoId2, packageName3, app3, locales)
+ appDao.insert(repoId3, packageName3, app3, locales)
+
+ // app1 and app2 are only in one repo, so that one is preferred
+ appPrefsDao.getPreferredRepos(listOf(packageName1, packageName2)).also { preferredRepos ->
+ assertEquals(2, preferredRepos.size)
+ assertEquals(repoId1, preferredRepos[packageName1])
+ assertEquals(repoId2, preferredRepos[packageName2])
}
- @Test
- fun testRemovingPreferredRepo() {
- // insert same app into three repos (repoId3 has highest weight)
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
- appDao.insert(repoId2, packageName, app3, locales)
-
- // app from preferred repo gets returned
- appPrefsDao.update(AppPrefs(packageName, preferredRepoId = repoId1))
- assertEquals(app1, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
-
- // preferred repo gets removed
- repoDao.deleteRepository(repoId1)
-
- // now app from repo with highest weight is returned
- assertEquals(app3, appDao.getApp(packageName).getOrFail()?.toMetadataV2()?.sort())
+ // preference only based on global repo priority/weight (3>2>1)
+ appPrefsDao.getPreferredRepos(listOf(packageName3, packageName2)).also { preferredRepos ->
+ assertEquals(2, preferredRepos.size)
+ assertEquals(repoId2, preferredRepos[packageName2])
+ assertEquals(repoId3, preferredRepos[packageName3])
}
- @Test
- fun testGetPreferredRepos() {
- // insert three apps, the third is in repo2 and repo3
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName1, app1, locales)
- appDao.insert(repoId2, packageName2, app2, locales)
- appDao.insert(repoId2, packageName3, app3, locales)
- appDao.insert(repoId3, packageName3, app3, locales)
-
- // app1 and app2 are only in one repo, so that one is preferred
- appPrefsDao.getPreferredRepos(listOf(packageName1, packageName2)).also { preferredRepos ->
- assertEquals(2, preferredRepos.size)
- assertEquals(repoId1, preferredRepos[packageName1])
- assertEquals(repoId2, preferredRepos[packageName2])
- }
-
- // preference only based on global repo priority/weight (3>2>1)
- appPrefsDao.getPreferredRepos(listOf(packageName3, packageName2)).also { preferredRepos ->
- assertEquals(2, preferredRepos.size)
- assertEquals(repoId2, preferredRepos[packageName2])
- assertEquals(repoId3, preferredRepos[packageName3])
- }
-
- // now app3 prefers repo2 explicitly
- appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
- appPrefsDao.getPreferredRepos(listOf(packageName3)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId2, preferredRepos[packageName3])
- }
-
- // repo2 removes app3 (oh no!), so preferred repo should fall back to repo3 again
- appDao.deleteAppMetadata(repoId2, packageName3)
- appPrefsDao.getPreferredRepos(listOf(packageName3)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId3, preferredRepos[packageName3])
- }
-
- // app3 prefers non-existing repo, so preferred repo should fall back to repo3 as well
- appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = 1337))
- appPrefsDao.getPreferredRepos(listOf(packageName, packageName3)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId3, preferredRepos[packageName3])
- }
-
- // app3 moves back to preferring repo3 and query for non-existent package name as well
- appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId3))
- appPrefsDao.getPreferredRepos(listOf(packageName, packageName3)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId3, preferredRepos[packageName3])
- }
+ // now app3 prefers repo2 explicitly
+ appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId2))
+ appPrefsDao.getPreferredRepos(listOf(packageName3)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId2, preferredRepos[packageName3])
}
- @Test
- fun getGetPreferredReposHandlesMaxVariableNumber() {
- // sqlite has a maximum number of 999 variables that can be used in a query
- val packagesOk = MutableList(998) { "" } + listOf(packageName)
- val packagesNotOk1 = MutableList(1000) { "" } + listOf(packageName)
- val packagesNotOk2 = MutableList(5000) { "" } + listOf(packageName)
-
- // insert same app in three repos
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1, locales)
- appDao.insert(repoId, packageName, app2, locales)
- appDao.insert(repoId, packageName, app3, locales)
-
- // preferred repos are returned as expected for all lists, no matter their size
- assertEquals(1, appPrefsDao.getPreferredRepos(packagesOk).size)
- assertEquals(1, appPrefsDao.getPreferredRepos(packagesNotOk1).size)
- assertEquals(1, appPrefsDao.getPreferredRepos(packagesNotOk2).size)
+ // repo2 removes app3 (oh no!), so preferred repo should fall back to repo3 again
+ appDao.deleteAppMetadata(repoId2, packageName3)
+ appPrefsDao.getPreferredRepos(listOf(packageName3)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId3, preferredRepos[packageName3])
}
- @Test
- fun getGetPreferredReposIgnoresDisabledRepos() {
- // insert one app into two repos
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
-
- // repo1 has a higher priority than repo2
- val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
- val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
- assertTrue(repoPrefs1.weight > repoPrefs2.weight)
-
- // repo1 is preferred due to higher weight
- appPrefsDao.getPreferredRepos(listOf(packageName)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId1, preferredRepos[packageName])
- }
-
- // disable repo1
- repoDao.updateRepositoryPreferences(repoPrefs1.copy(enabled = false))
-
- // now repo2 is preferred, because only enabled repo for that app
- appPrefsDao.getPreferredRepos(listOf(packageName)).also { preferredRepos ->
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId2, preferredRepos[packageName])
- }
+ // app3 prefers non-existing repo, so preferred repo should fall back to repo3 as well
+ appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = 1337))
+ appPrefsDao.getPreferredRepos(listOf(packageName, packageName3)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId3, preferredRepos[packageName3])
}
+ // app3 moves back to preferring repo3 and query for non-existent package name as well
+ appPrefsDao.update(AppPrefs(packageName3, preferredRepoId = repoId3))
+ appPrefsDao.getPreferredRepos(listOf(packageName, packageName3)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId3, preferredRepos[packageName3])
+ }
+ }
+
+ @Test
+ fun getGetPreferredReposHandlesMaxVariableNumber() {
+ // sqlite has a maximum number of 999 variables that can be used in a query
+ val packagesOk = MutableList(998) { "" } + listOf(packageName)
+ val packagesNotOk1 = MutableList(1000) { "" } + listOf(packageName)
+ val packagesNotOk2 = MutableList(5000) { "" } + listOf(packageName)
+
+ // insert same app in three repos
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1, locales)
+ appDao.insert(repoId, packageName, app2, locales)
+ appDao.insert(repoId, packageName, app3, locales)
+
+ // preferred repos are returned as expected for all lists, no matter their size
+ assertEquals(1, appPrefsDao.getPreferredRepos(packagesOk).size)
+ assertEquals(1, appPrefsDao.getPreferredRepos(packagesNotOk1).size)
+ assertEquals(1, appPrefsDao.getPreferredRepos(packagesNotOk2).size)
+ }
+
+ @Test
+ fun getGetPreferredReposIgnoresDisabledRepos() {
+ // insert one app into two repos
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+
+ // repo1 has a higher priority than repo2
+ val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
+ val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
+ assertTrue(repoPrefs1.weight > repoPrefs2.weight)
+
+ // repo1 is preferred due to higher weight
+ appPrefsDao.getPreferredRepos(listOf(packageName)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId1, preferredRepos[packageName])
+ }
+
+ // disable repo1
+ repoDao.updateRepositoryPreferences(repoPrefs1.copy(enabled = false))
+
+ // now repo2 is preferred, because only enabled repo for that app
+ appPrefsDao.getPreferredRepos(listOf(packageName)).also { preferredRepos ->
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId2, preferredRepos[packageName])
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt
index f4b41d9b7..f8ccb0ab7 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt
@@ -7,37 +7,45 @@ import org.fdroid.test.TestUtils.sort
internal abstract class AppTest : DbTest() {
- protected val packageName = getRandomString()
- protected val packageName1 = getRandomString()
- protected val packageName2 = getRandomString()
- protected val packageName3 = getRandomString()
- protected val name1 = mapOf("en-US" to "1")
- protected val name2 = mapOf("en-US" to "2")
- protected val name3 = mapOf("en-US" to "3")
+ protected val packageName = getRandomString()
+ protected val packageName1 = getRandomString()
+ protected val packageName2 = getRandomString()
+ protected val packageName3 = getRandomString()
+ protected val name1 = mapOf("en-US" to "1")
+ protected val name2 = mapOf("en-US" to "2")
+ protected val name3 = mapOf("en-US" to "3")
- // it is important for testing that the icons are sharing at least one locale
- protected val icons1 = mapOf("en-US" to getRandomFileV2(), "bar" to getRandomFileV2())
- protected val icons2 = mapOf("en-US" to getRandomFileV2(), "42" to getRandomFileV2())
- protected val app1 = getRandomMetadataV2().copy(
+ // it is important for testing that the icons are sharing at least one locale
+ protected val icons1 = mapOf("en-US" to getRandomFileV2(), "bar" to getRandomFileV2())
+ protected val icons2 = mapOf("en-US" to getRandomFileV2(), "42" to getRandomFileV2())
+ protected val app1 =
+ getRandomMetadataV2()
+ .copy(
name = name1,
icon = icons1,
summary = null,
lastUpdated = 10,
- categories = listOf("A", "B")
- ).sort()
- protected val app2 = getRandomMetadataV2().copy(
+ categories = listOf("A", "B"),
+ )
+ .sort()
+ protected val app2 =
+ getRandomMetadataV2()
+ .copy(
name = name2,
icon = icons2,
summary = name2,
lastUpdated = 20,
- categories = listOf("A")
- ).sort()
- protected val app3 = getRandomMetadataV2().copy(
+ categories = listOf("A"),
+ )
+ .sort()
+ protected val app3 =
+ getRandomMetadataV2()
+ .copy(
name = name3,
icon = null,
summary = name3,
lastUpdated = 30,
- categories = listOf("A", "B")
- ).sort()
-
+ categories = listOf("A", "B"),
+ )
+ .sort()
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/CountryCodeMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/CountryCodeMigrationTest.kt
index 011e7da5d..6617bebb5 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/CountryCodeMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/CountryCodeMigrationTest.kt
@@ -18,82 +18,93 @@ private const val TEST_DB = "migration-test"
@RunWith(AndroidJUnit4::class)
internal class CountryCodeMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- FDroidDatabaseInt::class.java,
- listOf(CountryCodeMigration()),
- FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ FDroidDatabaseInt::class.java,
+ listOf(CountryCodeMigration()),
+ FrameworkSQLiteOpenHelperFactory(),
)
- private val repo = InitialRepository(
- name = "F-Droid",
- address = "https://f-droid.org/repo",
- description = "The official F-Droid Free Software repository. " +
- "Everything in this repository is always built from the source code.",
- certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
- version = 13L,
- enabled = true,
- weight = 1,
+ private val repo =
+ InitialRepository(
+ name = "F-Droid",
+ address = "https://f-droid.org/repo",
+ description =
+ "The official F-Droid Free Software repository. " +
+ "Everything in this repository is always built from the source code.",
+ certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
+ version = 13L,
+ enabled = true,
+ weight = 1,
)
- @Test
- fun migrateCountryCode() {
- helper.createDatabase(TEST_DB, 7).use { db ->
- // Database has schema version 7. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- val repoId = db.insert(
- CoreRepository.TABLE,
- SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- put("name", Converters.localizedTextV2toString(mapOf("en-US" to repo.name)))
- put(
- "description",
- Converters.localizedTextV2toString(mapOf("en-US" to repo.description))
- )
- put("address", repo.address)
- put("timestamp", 42)
- put("certificate", repo.certificate)
- })
- db.insert(
- RepositoryPreferences.TABLE,
- SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("enabled", repo.enabled)
- put("lastETag", "foo")
- put("weight", repo.weight)
- })
- db.insert(
- Mirror.TABLE,
- SQLiteDatabase.CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("url", "foo")
- put("location", "bar")
- })
- }
-
- // Re-open the database with version 8, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 8, true).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- Room.databaseBuilder(
- ApplicationProvider.getApplicationContext(),
- FDroidDatabaseInt::class.java,
- TEST_DB
+ @Test
+ fun migrateCountryCode() {
+ helper.createDatabase(TEST_DB, 7).use { db ->
+ // Database has schema version 7. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ val repoId =
+ db.insert(
+ CoreRepository.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", Converters.localizedTextV2toString(mapOf("en-US" to repo.name)))
+ put(
+ "description",
+ Converters.localizedTextV2toString(mapOf("en-US" to repo.description)),
+ )
+ put("address", repo.address)
+ put("timestamp", 42)
+ put("certificate", repo.certificate)
+ },
)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build().use { db ->
- // check repo got timestamp and etag reset
- val repos = db.getRepositoryDao().getRepositories()
- assertEquals(1, repos.size)
- assertEquals(null, repos[0].lastETag)
- assertEquals(-1, repos[0].timestamp)
- // check mirror
- assertEquals(1, repos[0].mirrors.size)
- val mirror = repos[0].mirrors[0]
- assertEquals(repos[0].repoId, mirror.repoId)
- assertEquals("foo", mirror.url)
- assertEquals("bar", mirror.countryCode)
- }
+ db.insert(
+ RepositoryPreferences.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("enabled", repo.enabled)
+ put("lastETag", "foo")
+ put("weight", repo.weight)
+ },
+ )
+ db.insert(
+ Mirror.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("url", "foo")
+ put("location", "bar")
+ },
+ )
}
+
+ // Re-open the database with version 8, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 8, true).close()
+
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ Room.databaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ FDroidDatabaseInt::class.java,
+ TEST_DB,
+ )
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // check repo got timestamp and etag reset
+ val repos = db.getRepositoryDao().getRepositories()
+ assertEquals(1, repos.size)
+ assertEquals(null, repos[0].lastETag)
+ assertEquals(-1, repos[0].timestamp)
+ // check mirror
+ assertEquals(1, repos[0].mirrors.size)
+ val mirror = repos[0].mirrors[0]
+ assertEquals(repos[0].repoId, mirror.repoId)
+ assertEquals("foo", mirror.url)
+ assertEquals("bar", mirror.countryCode)
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt
index e4b31214c..b86939fc9 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt
@@ -9,6 +9,10 @@ import androidx.room.Room
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import io.mockk.every
import io.mockk.mockkObject
+import java.io.IOException
+import java.util.Locale
+import kotlin.test.assertEquals
+import kotlin.test.fail
import kotlinx.coroutines.Dispatchers
import org.fdroid.database.TestUtils.assertRepoEquals
import org.fdroid.database.TestUtils.getOrFail
@@ -17,6 +21,7 @@ import org.fdroid.database.TestUtils.toPackageVersionV2
import org.fdroid.index.v1.IndexV1StreamProcessor
import org.fdroid.index.v2.IndexV2
import org.fdroid.index.v2.IndexV2FullStreamProcessor
+import org.fdroid.test.TestUtils.getRes
import org.fdroid.test.TestUtils.sort
import org.fdroid.test.TestUtils.sorted
import org.fdroid.test.VerifierConstants.CERTIFICATE
@@ -24,119 +29,115 @@ import org.junit.After
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
-import java.io.IOException
-import java.util.Locale
-import kotlin.test.assertEquals
-import kotlin.test.fail
internal abstract class DbTest {
- @get:Rule
- val instantTaskExecutorRule = InstantTaskExecutorRule()
+ @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
- internal lateinit var repoDao: RepositoryDaoInt
- internal lateinit var appDao: AppDaoInt
- internal lateinit var appPrefsDao: AppPrefsDaoInt
- internal lateinit var versionDao: VersionDaoInt
- internal lateinit var db: FDroidDatabaseInt
- private val testCoroutineDispatcher = Dispatchers.Unconfined
+ internal lateinit var repoDao: RepositoryDaoInt
+ internal lateinit var appDao: AppDaoInt
+ internal lateinit var appPrefsDao: AppPrefsDaoInt
+ internal lateinit var versionDao: VersionDaoInt
+ internal lateinit var db: FDroidDatabaseInt
+ private val testCoroutineDispatcher = Dispatchers.Unconfined
- private val context: Context = getApplicationContext()
- protected val assets: AssetManager = context.resources.assets
- protected val locales = LocaleListCompat.create(Locale.US)
+ private val context: Context = getApplicationContext()
+ protected val assets: AssetManager = context.resources.assets
+ protected val locales = LocaleListCompat.create(Locale.US)
- @Before
- open fun createDb() {
- db = Room.inMemoryDatabaseBuilder(context, FDroidDatabaseInt::class.java)
- .allowMainThreadQueries()
- .build()
- repoDao = db.getRepositoryDao()
- appDao = db.getAppDao()
- appPrefsDao = db.getAppPrefsDao()
- versionDao = db.getVersionDao()
+ @Before
+ open fun createDb() {
+ db =
+ Room.inMemoryDatabaseBuilder(context, FDroidDatabaseInt::class.java)
+ .allowMainThreadQueries()
+ .build()
+ repoDao = db.getRepositoryDao()
+ appDao = db.getAppDao()
+ appPrefsDao = db.getAppPrefsDao()
+ versionDao = db.getVersionDao()
- // pre-Android P limitations for instrumentation tests (unit tests w/ robolectric are fine):
- // https://mockk.io/ANDROID#supported-features
- // See also: https://github.com/mockk/mockk/issues/182
- assumeTrue(Build.MODEL == "robolectric" || Build.VERSION.SDK_INT >= 28)
+ // pre-Android P limitations for instrumentation tests (unit tests w/ robolectric are fine):
+ // https://mockk.io/ANDROID#supported-features
+ // See also: https://github.com/mockk/mockk/issues/182
+ assumeTrue(Build.MODEL == "robolectric" || Build.VERSION.SDK_INT >= 28)
- mockkObject(FDroidDatabaseHolder)
- every { FDroidDatabaseHolder.dispatcher } returns testCoroutineDispatcher
+ mockkObject(FDroidDatabaseHolder)
+ every { FDroidDatabaseHolder.dispatcher } returns testCoroutineDispatcher
+ }
+
+ @After
+ @Throws(IOException::class)
+ fun closeDb() {
+ db.close()
+ }
+
+ protected fun streamIndexV1IntoDb(
+ indexAssetPath: String,
+ address: String = "https://f-droid.org/repo",
+ certificate: String = CERTIFICATE,
+ lastTimestamp: Long = -1,
+ ): Long {
+ val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate)
+ val streamReceiver = DbV1StreamReceiver(db, repoId) { true }
+ val indexProcessor = IndexV1StreamProcessor(streamReceiver, lastTimestamp)
+ db.runInTransaction {
+ getRes(indexAssetPath).use { indexStream -> indexProcessor.process(indexStream) }
}
+ return repoId
+ }
- @After
- @Throws(IOException::class)
- fun closeDb() {
- db.close()
+ protected fun streamIndexV2IntoDb(
+ indexAssetPath: String,
+ address: String = "https://f-droid.org/repo",
+ version: Long = 42L,
+ certificate: String = CERTIFICATE,
+ ): Long {
+ val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate)
+ val streamReceiver = DbV2StreamReceiver(db, repoId) { true }
+ val indexProcessor = IndexV2FullStreamProcessor(streamReceiver)
+ db.runInTransaction {
+ getRes(indexAssetPath).use { indexStream ->
+ indexProcessor.process(version, indexStream) {}
+ }
}
+ return repoId
+ }
- protected fun streamIndexV1IntoDb(
- indexAssetPath: String,
- address: String = "https://f-droid.org/repo",
- certificate: String = CERTIFICATE,
- lastTimestamp: Long = -1,
- ): Long {
- val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate)
- val streamReceiver = DbV1StreamReceiver(db, repoId) { true }
- val indexProcessor = IndexV1StreamProcessor(streamReceiver, lastTimestamp)
- db.runInTransaction {
- assets.open(indexAssetPath).use { indexStream ->
- indexProcessor.process(indexStream)
- }
- }
- return repoId
+ /** Asserts that data associated with the given [repoId] is equal to the given [index]. */
+ protected fun assertDbEquals(repoId: Long, index: IndexV2) {
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ val sortedIndex = index.sorted()
+ assertRepoEquals(sortedIndex.repo, repo)
+ assertEquals(sortedIndex.packages.size, appDao.countApps(), "number of packages")
+ sortedIndex.packages.forEach { (packageName, packageV2) ->
+ assertEquals(
+ // zero-whitespace hack needs to get applied to expected data
+ packageV2.metadata.copy(
+ name = packageV2.metadata.name.zero(),
+ summary = packageV2.metadata.summary.zero(),
+ description = packageV2.metadata.description.zero(),
+ ),
+ appDao.getApp(repoId, packageName)?.toMetadataV2()?.sort(),
+ )
+ val versions =
+ versionDao
+ .getAppVersions(repoId, packageName)
+ .getOrFail()
+ .map { it.toPackageVersionV2() }
+ .associateBy { it.file.sha256 }
+ assertEquals(packageV2.versions.size, versions.size, "number of versions")
+ packageV2.versions.forEach { (versionId, packageVersionV2) ->
+ val version = versions[versionId] ?: fail()
+ // filter out duplicate entries from index data
+ val v =
+ packageVersionV2.copy(
+ manifest =
+ packageVersionV2.manifest.copy(
+ usesPermission = packageVersionV2.manifest.usesPermission.toSet().toList()
+ )
+ )
+ assertEquals(v, version)
+ }
}
-
- protected fun streamIndexV2IntoDb(
- indexAssetPath: String,
- address: String = "https://f-droid.org/repo",
- version: Long = 42L,
- certificate: String = CERTIFICATE,
- ): Long {
- val repoId = db.getRepositoryDao().insertEmptyRepo(address, certificate = certificate)
- val streamReceiver = DbV2StreamReceiver(db, repoId) { true }
- val indexProcessor = IndexV2FullStreamProcessor(streamReceiver)
- db.runInTransaction {
- assets.open(indexAssetPath).use { indexStream ->
- indexProcessor.process(version, indexStream) {}
- }
- }
- return repoId
- }
-
- /**
- * Asserts that data associated with the given [repoId] is equal to the given [index].
- */
- protected fun assertDbEquals(repoId: Long, index: IndexV2) {
- val repo = repoDao.getRepository(repoId) ?: fail()
- val sortedIndex = index.sorted()
- assertRepoEquals(sortedIndex.repo, repo)
- assertEquals(sortedIndex.packages.size, appDao.countApps(), "number of packages")
- sortedIndex.packages.forEach { (packageName, packageV2) ->
- assertEquals(
- // zero-whitespace hack needs to get applied to expected data
- packageV2.metadata.copy(
- name = packageV2.metadata.name.zero(),
- summary = packageV2.metadata.summary.zero(),
- description = packageV2.metadata.description.zero(),
- ),
- appDao.getApp(repoId, packageName)?.toMetadataV2()?.sort()
- )
- val versions = versionDao.getAppVersions(repoId, packageName).getOrFail().map {
- it.toPackageVersionV2()
- }.associateBy { it.file.sha256 }
- assertEquals(packageV2.versions.size, versions.size, "number of versions")
- packageV2.versions.forEach { (versionId, packageVersionV2) ->
- val version = versions[versionId] ?: fail()
- // filter out duplicate entries from index data
- val v = packageVersionV2.copy(
- manifest = packageVersionV2.manifest.copy(
- usesPermission = packageVersionV2.manifest.usesPermission.toSet().toList()
- )
- )
- assertEquals(v, version)
- }
- }
- }
-
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/DbUpdateCheckerTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/DbUpdateCheckerTest.kt
index 57327014c..a1e9cdf16 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/DbUpdateCheckerTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/DbUpdateCheckerTest.kt
@@ -6,6 +6,12 @@ import android.content.pm.PackageManager.NameNotFoundException
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.mockk.every
import io.mockk.mockk
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.fdroid.index.RELEASE_CHANNEL_BETA
import org.fdroid.index.v2.PackageVersionV2
import org.fdroid.index.v2.SignerV2
@@ -16,321 +22,308 @@ import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
@Suppress("DEPRECATION")
@RunWith(AndroidJUnit4::class)
internal class DbUpdateCheckerTest : AppTest() {
- private lateinit var updateChecker: DbUpdateChecker
- private val packageManager: PackageManager = mockk()
- private val compatChecker: (PackageVersionV2) -> Boolean = { true }
+ private lateinit var updateChecker: DbUpdateChecker
+ private val packageManager: PackageManager = mockk()
+ private val compatChecker: (PackageVersionV2) -> Boolean = { true }
- private val packageInfo = PackageInfo().apply {
- packageName = TestDataMinV2.PACKAGE_NAME
- versionCode = 0
+ private val packageInfo =
+ PackageInfo().apply {
+ packageName = TestDataMinV2.PACKAGE_NAME
+ versionCode = 0
}
- @Before
- override fun createDb() {
- super.createDb()
- every { packageManager.systemAvailableFeatures } returns emptyArray()
- updateChecker = DbUpdateChecker(db, packageManager, { true })
+ @Before
+ override fun createDb() {
+ super.createDb()
+ every { packageManager.systemAvailableFeatures } returns emptyArray()
+ updateChecker = DbUpdateChecker(db, packageManager, { true })
+ }
+
+ @Test
+ fun testSuggestedVersion() {
+ val repoId = streamIndexV2IntoDb("index-min-v2.json")
+ every { packageManager.getPackageInfo(packageInfo.packageName, any()) } returns packageInfo
+ val appVersion = updateChecker.getSuggestedVersion(packageInfo.packageName)
+ val expectedVersion =
+ TestDataMinV2.version.toVersion(
+ repoId = repoId,
+ packageName = packageInfo.packageName,
+ versionId = TestDataMinV2.version.file.sha256,
+ isCompatible = true,
+ )
+ assertEquals(appVersion!!.version, expectedVersion)
+ }
+
+ @Test
+ fun testSuggestedVersionRespectsReleaseChannels() {
+ streamIndexV2IntoDb("index-mid-v2.json")
+ every { packageManager.getPackageInfo(packageInfo.packageName, any()) } returns null
+
+ // no suggestion version, because all beta
+ val appVersion1 = updateChecker.getSuggestedVersion(packageInfo.packageName)
+ assertNull(appVersion1)
+
+ // now suggests only available version
+ val appVersion2 =
+ updateChecker.getSuggestedVersion(
+ packageName = packageInfo.packageName,
+ releaseChannels = listOf(RELEASE_CHANNEL_BETA),
+ preferredSigner = TestDataMidV2.version1_2.signer!!.sha256[0],
+ )
+ assertEquals(TestDataMidV2.version1_2.versionCode, appVersion2!!.version.versionCode)
+ }
+
+ @Test
+ fun testSuggestedVersionRespectsPreferredSigner() {
+ // insert one app into the repo
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, app1.copy(), locales)
+
+ // two version have two different signers (correct format doesn't matter here)
+ val signer1 = SignerV2(listOf("foo", "bar"))
+ val signer2 = SignerV2(listOf("justOneSigner"))
+
+ // add two versions with the same version code, but different signers
+ val packageVersion1 = getRandomPackageVersionV2(versionCode = 42)
+ val packageVersion2 = getRandomPackageVersionV2(versionCode = 42)
+ val versionId1 = packageVersion1.file.sha256
+ val versionId2 = packageVersion2.file.sha256
+ val version1 =
+ packageVersion1
+ .copy(
+ manifest = packageVersion1.manifest.copy(signer = signer1),
+ releaseChannels = emptyList(),
+ )
+ .toVersion(repoId, packageName, versionId1, true)
+ val version2 =
+ packageVersion2
+ .copy(
+ manifest = packageVersion2.manifest.copy(signer = signer2),
+ releaseChannels = emptyList(),
+ )
+ .toVersion(repoId, packageName, versionId2, true)
+ versionDao.insert(version1)
+ versionDao.insert(version2)
+
+ // nothing is currently installed
+ every { packageManager.getPackageInfo(packageName, any()) } returns null
+
+ // if signer of first version is preferred first version is suggested as update
+ assertEquals(
+ version1,
+ updateChecker
+ .getSuggestedVersion(packageName = packageName, preferredSigner = signer1.sha256[0])
+ ?.version,
+ )
+
+ // if second signer of first version is preferred first version is suggested as update
+ assertEquals(
+ version1,
+ updateChecker
+ .getSuggestedVersion(packageName = packageName, preferredSigner = signer1.sha256[1])
+ ?.version,
+ )
+
+ // if signer of second version is preferred second version is suggested as update
+ assertEquals(
+ version2,
+ updateChecker
+ .getSuggestedVersion(packageName = packageName, preferredSigner = signer2.sha256[0])
+ ?.version,
+ )
+ }
+
+ @Test
+ fun testSuggestedVersionOnlyFromPreferredRepo() {
+ // insert the same app into two repos
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageName, app1, locales)
+ appDao.insert(repoId2, packageName, app2, locales)
+
+ // every app has a compatible version
+ val packageVersion1 =
+ mapOf("1" to getRandomPackageVersionV2(2, null).copy(releaseChannels = emptyList()))
+ val packageVersion2 =
+ mapOf("2" to getRandomPackageVersionV2(1, null).copy(releaseChannels = emptyList()))
+ versionDao.insert(repoId1, packageName, packageVersion1, compatChecker)
+ versionDao.insert(repoId2, packageName, packageVersion2, compatChecker)
+
+ // nothing is installed
+ every { packageManager.getPackageInfo(packageName, any()) } throws NameNotFoundException()
+
+ // without preferring repos, version with highest version code gets returned
+ updateChecker.getSuggestedVersion(packageName).also { appVersion ->
+ assertNotNull(appVersion)
+ assertEquals(repoId1, appVersion.repoId)
+ assertEquals(2, appVersion.manifest.versionCode)
}
- @Test
- fun testSuggestedVersion() {
- val repoId = streamIndexV2IntoDb("index-min-v2.json")
- every {
- packageManager.getPackageInfo(packageInfo.packageName, any())
- } returns packageInfo
- val appVersion = updateChecker.getSuggestedVersion(packageInfo.packageName)
- val expectedVersion = TestDataMinV2.version.toVersion(
- repoId = repoId,
- packageName = packageInfo.packageName,
- versionId = TestDataMinV2.version.file.sha256,
- isCompatible = true,
- )
- assertEquals(appVersion!!.version, expectedVersion)
+ // now we want versions only from preferred repo and get the one with highest weight
+ updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true).also { appVersion
+ ->
+ assertNotNull(appVersion)
+ assertEquals(repoId2, appVersion.repoId)
+ assertEquals(1, appVersion.manifest.versionCode)
}
- @Test
- fun testSuggestedVersionRespectsReleaseChannels() {
- streamIndexV2IntoDb("index-mid-v2.json")
- every { packageManager.getPackageInfo(packageInfo.packageName, any()) } returns null
-
- // no suggestion version, because all beta
- val appVersion1 = updateChecker.getSuggestedVersion(packageInfo.packageName)
- assertNull(appVersion1)
-
- // now suggests only available version
- val appVersion2 = updateChecker.getSuggestedVersion(
- packageName = packageInfo.packageName,
- releaseChannels = listOf(RELEASE_CHANNEL_BETA),
- preferredSigner = TestDataMidV2.version1_2.signer!!.sha256[0],
- )
- assertEquals(TestDataMidV2.version1_2.versionCode, appVersion2!!.version.versionCode)
+ // now we allow all repos, but explicitly prefer repo 1, getting same result as above
+ appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId1))
+ updateChecker.getSuggestedVersion(packageName).also { appVersion ->
+ assertNotNull(appVersion)
+ assertEquals(repoId1, appVersion.repoId)
+ assertEquals(2, appVersion.manifest.versionCode)
}
- @Test
- fun testSuggestedVersionRespectsPreferredSigner() {
- // insert one app into the repo
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, app1.copy(), locales)
-
- // two version have two different signers (correct format doesn't matter here)
- val signer1 = SignerV2(listOf("foo", "bar"))
- val signer2 = SignerV2(listOf("justOneSigner"))
-
- // add two versions with the same version code, but different signers
- val packageVersion1 = getRandomPackageVersionV2(versionCode = 42)
- val packageVersion2 = getRandomPackageVersionV2(versionCode = 42)
- val versionId1 = packageVersion1.file.sha256
- val versionId2 = packageVersion2.file.sha256
- val version1 = packageVersion1.copy(
- manifest = packageVersion1.manifest.copy(signer = signer1),
- releaseChannels = emptyList(),
- ).toVersion(repoId, packageName, versionId1, true)
- val version2 = packageVersion2.copy(
- manifest = packageVersion2.manifest.copy(signer = signer2),
- releaseChannels = emptyList(),
- ).toVersion(repoId, packageName, versionId2, true)
- versionDao.insert(version1)
- versionDao.insert(version2)
-
- // nothing is currently installed
- every { packageManager.getPackageInfo(packageName, any()) } returns null
-
- // if signer of first version is preferred first version is suggested as update
- assertEquals(
- version1,
- updateChecker.getSuggestedVersion(
- packageName = packageName,
- preferredSigner = signer1.sha256[0]
- )?.version,
- )
-
- // if second signer of first version is preferred first version is suggested as update
- assertEquals(
- version1,
- updateChecker.getSuggestedVersion(
- packageName = packageName,
- preferredSigner = signer1.sha256[1]
- )?.version,
- )
-
- // if signer of second version is preferred second version is suggested as update
- assertEquals(
- version2,
- updateChecker.getSuggestedVersion(
- packageName = packageName,
- preferredSigner = signer2.sha256[0]
- )?.version,
- )
+ // now we prefer repo 2 and only want versions from preferred repo
+ appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId2))
+ updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true).also { appVersion
+ ->
+ assertNotNull(appVersion)
+ assertEquals(repoId2, appVersion.repoId)
+ assertEquals(1, appVersion.manifest.versionCode)
}
- @Test
- fun testSuggestedVersionOnlyFromPreferredRepo() {
- // insert the same app into two repos
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageName, app1, locales)
- appDao.insert(repoId2, packageName, app2, locales)
+ // now we have version 1 already installed
+ every { packageManager.getPackageInfo(packageName, any()) } returns
+ PackageInfo().apply {
+ packageName = this@DbUpdateCheckerTest.packageName
+ versionCode = 1
+ }
- // every app has a compatible version
- val packageVersion1 = mapOf(
- "1" to getRandomPackageVersionV2(2, null).copy(releaseChannels = emptyList())
- )
- val packageVersion2 = mapOf(
- "2" to getRandomPackageVersionV2(1, null).copy(releaseChannels = emptyList())
- )
- versionDao.insert(repoId1, packageName, packageVersion1, compatChecker)
- versionDao.insert(repoId2, packageName, packageVersion2, compatChecker)
-
- // nothing is installed
- every {
- packageManager.getPackageInfo(packageName, any())
- } throws NameNotFoundException()
-
- // without preferring repos, version with highest version code gets returned
- updateChecker.getSuggestedVersion(packageName).also { appVersion ->
- assertNotNull(appVersion)
- assertEquals(repoId1, appVersion.repoId)
- assertEquals(2, appVersion.manifest.versionCode)
- }
-
- // now we want versions only from preferred repo and get the one with highest weight
- updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true)
- .also { appVersion ->
- assertNotNull(appVersion)
- assertEquals(repoId2, appVersion.repoId)
- assertEquals(1, appVersion.manifest.versionCode)
- }
-
- // now we allow all repos, but explicitly prefer repo 1, getting same result as above
- appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId1))
- updateChecker.getSuggestedVersion(packageName).also { appVersion ->
- assertNotNull(appVersion)
- assertEquals(repoId1, appVersion.repoId)
- assertEquals(2, appVersion.manifest.versionCode)
- }
-
- // now we prefer repo 2 and only want versions from preferred repo
- appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId2))
- updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true)
- .also { appVersion ->
- assertNotNull(appVersion)
- assertEquals(repoId2, appVersion.repoId)
- assertEquals(1, appVersion.manifest.versionCode)
- }
-
- // now we have version 1 already installed
- every {
- packageManager.getPackageInfo(packageName, any())
- } returns PackageInfo().apply {
- packageName = this@DbUpdateCheckerTest.packageName
- versionCode = 1
- }
-
- // preferred repos don't have suggested versions
- updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true)
- .also { appVersion ->
- assertNull(appVersion)
- }
-
- // but other repos still have
- updateChecker.getSuggestedVersion(packageName).also { appVersion ->
- assertNotNull(appVersion)
- assertEquals(repoId1, appVersion.repoId)
- assertEquals(2, appVersion.manifest.versionCode)
- }
+ // preferred repos don't have suggested versions
+ updateChecker.getSuggestedVersion(packageName, onlyFromPreferredRepo = true).also { appVersion
+ ->
+ assertNull(appVersion)
}
- @Test
- fun testGetUpdatableApps() {
- streamIndexV2IntoDb("index-min-v2.json")
- every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
+ // but other repos still have
+ updateChecker.getSuggestedVersion(packageName).also { appVersion ->
+ assertNotNull(appVersion)
+ assertEquals(repoId1, appVersion.repoId)
+ assertEquals(2, appVersion.manifest.versionCode)
+ }
+ }
- val appVersions = updateChecker.getUpdatableApps()
- assertEquals(1, appVersions.size)
- assertEquals(0, appVersions[0].installedVersionCode)
- assertEquals(TestDataMinV2.PACKAGE_NAME, appVersions[0].packageName)
- assertEquals(TestDataMinV2.version.file.sha256, appVersions[0].update.version.versionId)
+ @Test
+ fun testGetUpdatableApps() {
+ streamIndexV2IntoDb("index-min-v2.json")
+ every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
+
+ val appVersions = updateChecker.getUpdatableApps()
+ assertEquals(1, appVersions.size)
+ assertEquals(0, appVersions[0].installedVersionCode)
+ assertEquals(TestDataMinV2.PACKAGE_NAME, appVersions[0].packageName)
+ assertEquals(TestDataMinV2.version.file.sha256, appVersions[0].update.version.versionId)
+ }
+
+ @Test
+ fun testGetUpdatableAppsOnlyFromPreferredRepo() {
+ // insert the same app into three repos
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageInfo.packageName, app1, locales)
+ appDao.insert(repoId2, packageInfo.packageName, app2, locales)
+ appDao.insert(repoId3, packageInfo.packageName, app3, locales)
+
+ // every app has a compatible update (versionCode greater than 0)
+ val packageVersion1 =
+ mapOf("1" to getRandomPackageVersionV2(13, null).copy(releaseChannels = emptyList()))
+ val packageVersion2 =
+ mapOf("2" to getRandomPackageVersionV2(12, null).copy(releaseChannels = emptyList()))
+ val packageVersion3 =
+ mapOf("3" to getRandomPackageVersionV2(10, null).copy(releaseChannels = emptyList()))
+ versionDao.insert(repoId1, packageInfo.packageName, packageVersion1, compatChecker)
+ versionDao.insert(repoId2, packageInfo.packageName, packageVersion2, compatChecker)
+ versionDao.insert(repoId3, packageInfo.packageName, packageVersion3, compatChecker)
+
+ // app is installed with version code 0
+ assertEquals(0, packageInfo.versionCode)
+ every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
+
+ // without preferring repos, version with highest version code gets returned
+ updateChecker.getUpdatableApps().also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId1, appVersions[0].repoId)
+ assertEquals(13, appVersions[0].update.manifest.versionCode)
+ assertFalse(appVersions[0].isFromPreferredRepo) // preferred repo is 3 per weight
}
- @Test
- fun testGetUpdatableAppsOnlyFromPreferredRepo() {
- // insert the same app into three repos
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageInfo.packageName, app1, locales)
- appDao.insert(repoId2, packageInfo.packageName, app2, locales)
- appDao.insert(repoId3, packageInfo.packageName, app3, locales)
-
- // every app has a compatible update (versionCode greater than 0)
- val packageVersion1 = mapOf(
- "1" to getRandomPackageVersionV2(13, null).copy(releaseChannels = emptyList())
- )
- val packageVersion2 = mapOf(
- "2" to getRandomPackageVersionV2(12, null).copy(releaseChannels = emptyList())
- )
- val packageVersion3 = mapOf(
- "3" to getRandomPackageVersionV2(10, null).copy(releaseChannels = emptyList())
- )
- versionDao.insert(repoId1, packageInfo.packageName, packageVersion1, compatChecker)
- versionDao.insert(repoId2, packageInfo.packageName, packageVersion2, compatChecker)
- versionDao.insert(repoId3, packageInfo.packageName, packageVersion3, compatChecker)
-
- // app is installed with version code 0
- assertEquals(0, packageInfo.versionCode)
- every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
-
- // without preferring repos, version with highest version code gets returned
- updateChecker.getUpdatableApps().also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId1, appVersions[0].repoId)
- assertEquals(13, appVersions[0].update.manifest.versionCode)
- assertFalse(appVersions[0].isFromPreferredRepo) // preferred repo is 3 per weight
- }
-
- // now we want versions only from preferred repo and get the one with highest weight
- updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId3, appVersions[0].repoId)
- assertEquals(10, appVersions[0].update.manifest.versionCode)
- assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 3 due to weight
- }
-
- // now we allow all repos, but explicitly prefer repo 1, isFromPreferredRepo becomes true
- appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId1))
- updateChecker.getUpdatableApps().also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId1, appVersions[0].repoId)
- assertEquals(13, appVersions[0].update.manifest.versionCode)
- assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 1 now
- }
-
- // now we prefer repo 2 and only want versions from preferred repo
- appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId2))
- updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId2, appVersions[0].repoId)
- assertEquals(12, appVersions[0].update.manifest.versionCode)
- assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 2 now
- }
+ // now we want versions only from preferred repo and get the one with highest weight
+ updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId3, appVersions[0].repoId)
+ assertEquals(10, appVersions[0].update.manifest.versionCode)
+ assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 3 due to weight
}
- @Test
- fun testGetUpdatableAppsUnaffectedByDisabledRepos() {
- // insert the same app into two repos
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId1, packageInfo.packageName, app1, locales)
- appDao.insert(repoId2, packageInfo.packageName, app2, locales)
-
- // repo1 has a higher priority than repo2
- val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
- val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
- assertTrue(repoPrefs1.weight > repoPrefs2.weight)
-
- // both apps a compatible update (versionCode greater than 0)
- val packageVersion1 = mapOf(
- "1" to getRandomPackageVersionV2(12, null).copy(releaseChannels = emptyList())
- )
- val packageVersion2 = mapOf(
- "2" to getRandomPackageVersionV2(13, null).copy(releaseChannels = emptyList())
- )
- versionDao.insert(repoId1, packageInfo.packageName, packageVersion1, compatChecker)
- versionDao.insert(repoId2, packageInfo.packageName, packageVersion2, compatChecker)
-
- // app is installed with version code 0
- assertEquals(0, packageInfo.versionCode)
- every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
-
- // version from repo with highest priority (1) gets returned
- updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId1, appVersions[0].repoId)
- assertEquals(12, appVersions[0].update.manifest.versionCode)
- assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 1 per weight
- }
-
- // disable repo1
- repoDao.updateRepositoryPreferences(repoPrefs1.copy(enabled = false))
-
- // now update from remaining enabled repo should get returned
- updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
- assertEquals(1, appVersions.size)
- assertEquals(repoId2, appVersions[0].repoId)
- assertEquals(13, appVersions[0].update.manifest.versionCode)
- assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 2
- }
+ // now we allow all repos, but explicitly prefer repo 1, isFromPreferredRepo becomes true
+ appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId1))
+ updateChecker.getUpdatableApps().also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId1, appVersions[0].repoId)
+ assertEquals(13, appVersions[0].update.manifest.versionCode)
+ assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 1 now
}
+ // now we prefer repo 2 and only want versions from preferred repo
+ appPrefsDao.update(AppPrefs(packageInfo.packageName, preferredRepoId = repoId2))
+ updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId2, appVersions[0].repoId)
+ assertEquals(12, appVersions[0].update.manifest.versionCode)
+ assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 2 now
+ }
+ }
+
+ @Test
+ fun testGetUpdatableAppsUnaffectedByDisabledRepos() {
+ // insert the same app into two repos
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId1, packageInfo.packageName, app1, locales)
+ appDao.insert(repoId2, packageInfo.packageName, app2, locales)
+
+ // repo1 has a higher priority than repo2
+ val repoPrefs1 = repoDao.getRepositoryPreferences(repoId1) ?: fail()
+ val repoPrefs2 = repoDao.getRepositoryPreferences(repoId2) ?: fail()
+ assertTrue(repoPrefs1.weight > repoPrefs2.weight)
+
+ // both apps a compatible update (versionCode greater than 0)
+ val packageVersion1 =
+ mapOf("1" to getRandomPackageVersionV2(12, null).copy(releaseChannels = emptyList()))
+ val packageVersion2 =
+ mapOf("2" to getRandomPackageVersionV2(13, null).copy(releaseChannels = emptyList()))
+ versionDao.insert(repoId1, packageInfo.packageName, packageVersion1, compatChecker)
+ versionDao.insert(repoId2, packageInfo.packageName, packageVersion2, compatChecker)
+
+ // app is installed with version code 0
+ assertEquals(0, packageInfo.versionCode)
+ every { packageManager.getInstalledPackages(any()) } returns listOf(packageInfo)
+
+ // version from repo with highest priority (1) gets returned
+ updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId1, appVersions[0].repoId)
+ assertEquals(12, appVersions[0].update.manifest.versionCode)
+ assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 1 per weight
+ }
+
+ // disable repo1
+ repoDao.updateRepositoryPreferences(repoPrefs1.copy(enabled = false))
+
+ // now update from remaining enabled repo should get returned
+ updateChecker.getUpdatableApps(onlyFromPreferredRepo = true).also { appVersions ->
+ assertEquals(1, appVersions.size)
+ assertEquals(repoId2, appVersions[0].repoId)
+ assertEquals(13, appVersions[0].update.manifest.versionCode)
+ assertTrue(appVersions[0].isFromPreferredRepo) // preferred repo is 2
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/FDroidDatabaseTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/FDroidDatabaseTest.kt
index 072977fb5..e23ddba0f 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/FDroidDatabaseTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/FDroidDatabaseTest.kt
@@ -1,44 +1,39 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
import org.fdroid.test.TestRepoUtils
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertNotEquals
@RunWith(AndroidJUnit4::class)
internal class FDroidDatabaseTest : AppTest() {
- @Test
- fun testClearAllAppData() {
- // insert three apps in two repos
- val repoId2 = repoDao.insertOrReplace(TestRepoUtils.getRandomRepo())
- val repoId1 = repoDao.insertOrReplace(TestRepoUtils.getRandomRepo())
- appDao.insert(repoId1, packageName1, app1, locales)
- appDao.insert(repoId2, packageName2, app2, locales)
- appDao.insert(repoId2, packageName3, app3, locales)
+ @Test
+ fun testClearAllAppData() {
+ // insert three apps in two repos
+ val repoId2 = repoDao.insertOrReplace(TestRepoUtils.getRandomRepo())
+ val repoId1 = repoDao.insertOrReplace(TestRepoUtils.getRandomRepo())
+ appDao.insert(repoId1, packageName1, app1, locales)
+ appDao.insert(repoId2, packageName2, app2, locales)
+ appDao.insert(repoId2, packageName3, app3, locales)
- // assert that both repos and all three apps made it into the DB
- assertEquals(2, repoDao.getRepositories().size)
- assertEquals(3, appDao.countApps())
+ // assert that both repos and all three apps made it into the DB
+ assertEquals(2, repoDao.getRepositories().size)
+ assertEquals(3, appDao.countApps())
- // assert that repo timestamps are recent, not reset
- repoDao.getRepositories().forEach { repo ->
- assertNotEquals(-1, repo.timestamp)
- }
+ // assert that repo timestamps are recent, not reset
+ repoDao.getRepositories().forEach { repo -> assertNotEquals(-1, repo.timestamp) }
- // clear all app data
- db.clearAllAppData()
+ // clear all app data
+ db.clearAllAppData()
- // assert that both repos survived, but all apps are gone
- assertEquals(2, repoDao.getRepositories().size)
- assertEquals(0, appDao.countApps())
-
- // assert that repo timestamps got reset
- repoDao.getRepositories().forEach { repo ->
- assertEquals(-1, repo.timestamp)
- }
- }
+ // assert that both repos survived, but all apps are gone
+ assertEquals(2, repoDao.getRepositories().size)
+ assertEquals(0, appDao.countApps())
+ // assert that repo timestamps got reset
+ repoDao.getRepositories().forEach { repo -> assertEquals(-1, repo.timestamp) }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/FtsAddColumnsMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/FtsAddColumnsMigrationTest.kt
index 3e006705f..b9e0670c2 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/FtsAddColumnsMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/FtsAddColumnsMigrationTest.kt
@@ -11,230 +11,229 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.random.Random
+import kotlin.test.assertEquals
import kotlinx.coroutines.runBlocking
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.test.TestUtils.getRandomString
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
private const val TEST_DB = "fts-test"
@RunWith(AndroidJUnit4::class)
internal class FtsAddColumnsMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- instrumentation = InstrumentationRegistry.getInstrumentation(),
- databaseClass = FDroidDatabaseInt::class.java,
- specs = emptyList(),
- openFactory = FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ instrumentation = InstrumentationRegistry.getInstrumentation(),
+ databaseClass = FDroidDatabaseInt::class.java,
+ specs = emptyList(),
+ openFactory = FrameworkSQLiteOpenHelperFactory(),
)
- @get:Rule
- val instantExec = InstantTaskExecutorRule()
+ @get:Rule val instantExec = InstantTaskExecutorRule()
- private val context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
- private val repo = ContentValues().apply {
- put("repoId", 1)
- put("name", Converters.localizedTextV2toString(mapOf("de" to "a", "en-US" to "b")))
- put("address", getRandomString())
- put("certificate", "abcdef")
- put("description", Converters.localizedTextV2toString(mapOf("de" to "aa", "en-US" to "bb")))
- put("version", Random.nextLong())
- put("timestamp", Random.nextLong())
+ private val repo =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("name", Converters.localizedTextV2toString(mapOf("de" to "a", "en-US" to "b")))
+ put("address", getRandomString())
+ put("certificate", "abcdef")
+ put("description", Converters.localizedTextV2toString(mapOf("de" to "aa", "en-US" to "bb")))
+ put("version", Random.nextLong())
+ put("timestamp", Random.nextLong())
}
- private val repoPrefs = ContentValues().apply {
- put("repoId", 1)
- put("enabled", true)
- put("weight", 1)
+ private val repoPrefs =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("enabled", true)
+ put("weight", 1)
}
- private val oeffiMetadata = ContentValues().apply {
- put("packageName", "de.schildbach.oeffi")
- put("repoId", 1)
- put(
- "name", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffi", "en-US" to "Offi"
- )
- )
- )
- put(
- "description", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
- )
- )
- )
- put("license", "GPL-3.0")
- put(
- "summary", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Der König des Fahrplandschungels!",
- "en-US" to " King of public transit planning!"
- )
- )
- )
- put("localizedName", "Öffi")
- put("localizedSummary", "Der König des Fahrplandschungels!")
- put("added", Random.nextLong())
- put("lastUpdated", Random.nextLong())
- put("isCompatible", true)
+ private val oeffiMetadata =
+ ContentValues().apply {
+ put("packageName", "de.schildbach.oeffi")
+ put("repoId", 1)
+ put("name", Converters.localizedTextV2toString(mapOf("de" to "Öffi", "en-US" to "Offi")))
+ put(
+ "description",
+ Converters.localizedTextV2toString(
+ mapOf("de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")
+ ),
+ )
+ put("license", "GPL-3.0")
+ put(
+ "summary",
+ Converters.localizedTextV2toString(
+ mapOf(
+ "de" to "Der König des Fahrplandschungels!",
+ "en-US" to " King of public transit planning!",
+ )
+ ),
+ )
+ put("localizedName", "Öffi")
+ put("localizedSummary", "Der König des Fahrplandschungels!")
+ put("added", Random.nextLong())
+ put("lastUpdated", Random.nextLong())
+ put("isCompatible", true)
}
- private val oeffiVersion = ContentValues().apply {
- put("repoId", 1)
- put("packageName", "de.schildbach.oeffi")
- put("versionId", 1)
- put("added", 1000)
- put("isCompatible", true)
- put("file_name", "transportr.apk")
- put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
- put("manifest_versionName", "1.0.0")
- put("manifest_versionCode", 1)
+ private val oeffiVersion =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("packageName", "de.schildbach.oeffi")
+ put("versionId", 1)
+ put("added", 1000)
+ put("isCompatible", true)
+ put("file_name", "transportr.apk")
+ put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
+ put("manifest_versionName", "1.0.0")
+ put("manifest_versionCode", 1)
}
- private val transportrMetadata = ContentValues().apply {
- put("packageName", "de.grobox.liberario")
- put("repoId", 1)
- put(
- "name", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Transportr", "en-US" to "Transportr"
- )
- )
- )
- put(
- "description", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
- )
- )
- )
- put("license", "GPL-3.0")
- put(
- "summary", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung",
- "en-US" to "Free Public Transport Assistant without Ads or Tracking"
- )
- )
- )
- put("localizedName", "Transportr")
- put(
- "localizedSummary",
- "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking"
- )
- put("added", Random.nextLong())
- put("lastUpdated", Random.nextLong())
- put("isCompatible", true)
+ private val transportrMetadata =
+ ContentValues().apply {
+ put("packageName", "de.grobox.liberario")
+ put("repoId", 1)
+ put(
+ "name",
+ Converters.localizedTextV2toString(mapOf("de" to "Transportr", "en-US" to "Transportr")),
+ )
+ put(
+ "description",
+ Converters.localizedTextV2toString(
+ mapOf("de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")
+ ),
+ )
+ put("license", "GPL-3.0")
+ put(
+ "summary",
+ Converters.localizedTextV2toString(
+ mapOf(
+ "de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung",
+ "en-US" to "Free Public Transport Assistant without Ads or Tracking",
+ )
+ ),
+ )
+ put("localizedName", "Transportr")
+ put(
+ "localizedSummary",
+ "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking",
+ )
+ put("added", Random.nextLong())
+ put("lastUpdated", Random.nextLong())
+ put("isCompatible", true)
}
- private val transportrVersion = ContentValues().apply {
- put("repoId", 1)
- put("packageName", "de.grobox.liberario")
- put("versionId", 1)
- put("added", 1000)
- put("isCompatible", true)
- put("file_name", "transportr.apk")
- put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
- put("manifest_versionName", "1.0.0")
- put("manifest_versionCode", 1)
+ private val transportrVersion =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("packageName", "de.grobox.liberario")
+ put("versionId", 1)
+ put("added", 1000)
+ put("isCompatible", true)
+ put("file_name", "transportr.apk")
+ put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
+ put("manifest_versionName", "1.0.0")
+ put("manifest_versionCode", 1)
}
- @Test
- fun testMigration() = runBlocking {
- helper.createDatabase(TEST_DB, 8).use { db ->
- // Database has schema version 8. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- db.insert(CoreRepository.TABLE, CONFLICT_FAIL, repo)
- db.insert(RepositoryPreferences.TABLE, CONFLICT_FAIL, repoPrefs)
- db.insert(AppMetadata.TABLE, CONFLICT_FAIL, oeffiMetadata)
- db.insert(AppMetadata.TABLE, CONFLICT_FAIL, transportrMetadata)
- db.insert(Version.TABLE, CONFLICT_FAIL, oeffiVersion)
- db.insert(Version.TABLE, CONFLICT_FAIL, transportrVersion)
+ @Test
+ fun testMigration() = runBlocking {
+ helper.createDatabase(TEST_DB, 8).use { db ->
+ // Database has schema version 8. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ db.insert(CoreRepository.TABLE, CONFLICT_FAIL, repo)
+ db.insert(RepositoryPreferences.TABLE, CONFLICT_FAIL, repoPrefs)
+ db.insert(AppMetadata.TABLE, CONFLICT_FAIL, oeffiMetadata)
+ db.insert(AppMetadata.TABLE, CONFLICT_FAIL, transportrMetadata)
+ db.insert(Version.TABLE, CONFLICT_FAIL, oeffiVersion)
+ db.insert(Version.TABLE, CONFLICT_FAIL, transportrVersion)
- // default search with no diacritics
- assertSearch(db, "*Transport*", 1)
- assertSearch(db, "Transportr", 1)
- assertSearch(db, "*f*", 2)
- // no or wrong diacritics
- assertSearch(db, "Offi", 0)
- assertSearch(db, "offi", 0)
- assertSearch(db, "õffi", 0)
- assertSearch(db, "*Offi*", 0)
- assertSearch(db, "*offi*", 0)
- assertSearch(db, "Konig", 0)
- // correct diacritics
- assertSearch(db, "*Öffi*", 1)
- assertSearch(db, "*öffi*", 1)
- assertSearch(db, "Öffi", 1)
- assertSearch(db, "öffi", 1)
- // both apps have "öff" in their name or summary
- assertSearch(db, "*öff*", 2)
- assertSearch(db, "König", 1)
- }
-
- helper.runMigrationsAndValidate(TEST_DB, 9, true, MIGRATION_8_9).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- Room.databaseBuilder(context, FDroidDatabaseInt::class.java, TEST_DB)
- .allowMainThreadQueries()
- .build().use { db ->
- // assert that apps are still there
- val metadata = db.getAppDao().getAppMetadata()
- assertEquals(2, metadata.size)
- // default search with no diacritics
- assertGetAppListItems(db, "*Transport*", 2)
- assertGetAppListItems(db, "Transportr", 1)
- assertGetAppListItems(db, "*f*", 2)
- // no or wrong diacritics also produces results now
- assertGetAppListItems(db, "Offi", 1)
- assertGetAppListItems(db, "offi", 1)
- assertGetAppListItems(db, "õffi", 1)
- assertGetAppListItems(db, "*Offi*", 1)
- assertGetAppListItems(db, "*offi*", 1)
- assertGetAppListItems(db, "Konig", 1)
- // correct diacritics still produces results
- assertGetAppListItems(db, "*Öffi*", 1)
- assertGetAppListItems(db, "*öffi*", 1)
- assertGetAppListItems(db, "Öffi", 1)
- assertGetAppListItems(db, "öffi", 1)
- // both apps have "öff" in their name or summary
- assertGetAppListItems(db, "*öff*", 2)
- assertGetAppListItems(db, "König", 1)
-
- // a new app also gets updated in the index
- assertGetAppListItems(db, "Rosa", 0)
- assertGetAppListItems(db, "Elefant", 0)
- val newApp = AppMetadata(
- repoId = 1,
- packageName = "org.example",
- added = 23,
- lastUpdated = 42,
- name = mapOf("de_DE" to "Rosa Elefant"),
- isCompatible = true
- )
- db.getAppDao().insert(newApp)
- assertGetAppListItems(db, "Rosa", 1)
- assertGetAppListItems(db, "Elefant", 1)
- }
+ // default search with no diacritics
+ assertSearch(db, "*Transport*", 1)
+ assertSearch(db, "Transportr", 1)
+ assertSearch(db, "*f*", 2)
+ // no or wrong diacritics
+ assertSearch(db, "Offi", 0)
+ assertSearch(db, "offi", 0)
+ assertSearch(db, "õffi", 0)
+ assertSearch(db, "*Offi*", 0)
+ assertSearch(db, "*offi*", 0)
+ assertSearch(db, "Konig", 0)
+ // correct diacritics
+ assertSearch(db, "*Öffi*", 1)
+ assertSearch(db, "*öffi*", 1)
+ assertSearch(db, "Öffi", 1)
+ assertSearch(db, "öffi", 1)
+ // both apps have "öff" in their name or summary
+ assertSearch(db, "*öff*", 2)
+ assertSearch(db, "König", 1)
}
- private fun assertSearch(db: SupportSQLiteDatabase, query: String, expected: Int) {
- db.query("SELECT * FROM AppMetadataFts WHERE AppMetadataFts MATCH '$query'")
- .use { cursor -> assertEquals(expected, cursor.count) }
- }
+ helper.runMigrationsAndValidate(TEST_DB, 9, true, MIGRATION_8_9).close()
- private fun assertGetAppListItems(db: FDroidDatabaseInt, query: String, expected: Int) {
- db.getAppDao().getAppListItems(query).getOrFail().let { result ->
- assertEquals(expected, result.size, "${result.map { it.name }}")
- }
- }
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ Room.databaseBuilder(context, FDroidDatabaseInt::class.java, TEST_DB)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // assert that apps are still there
+ val metadata = db.getAppDao().getAppMetadata()
+ assertEquals(2, metadata.size)
+ // default search with no diacritics
+ assertGetAppListItems(db, "*Transport*", 2)
+ assertGetAppListItems(db, "Transportr", 1)
+ assertGetAppListItems(db, "*f*", 2)
+ // no or wrong diacritics also produces results now
+ assertGetAppListItems(db, "Offi", 1)
+ assertGetAppListItems(db, "offi", 1)
+ assertGetAppListItems(db, "õffi", 1)
+ assertGetAppListItems(db, "*Offi*", 1)
+ assertGetAppListItems(db, "*offi*", 1)
+ assertGetAppListItems(db, "Konig", 1)
+ // correct diacritics still produces results
+ assertGetAppListItems(db, "*Öffi*", 1)
+ assertGetAppListItems(db, "*öffi*", 1)
+ assertGetAppListItems(db, "Öffi", 1)
+ assertGetAppListItems(db, "öffi", 1)
+ // both apps have "öff" in their name or summary
+ assertGetAppListItems(db, "*öff*", 2)
+ assertGetAppListItems(db, "König", 1)
+ // a new app also gets updated in the index
+ assertGetAppListItems(db, "Rosa", 0)
+ assertGetAppListItems(db, "Elefant", 0)
+ val newApp =
+ AppMetadata(
+ repoId = 1,
+ packageName = "org.example",
+ added = 23,
+ lastUpdated = 42,
+ name = mapOf("de_DE" to "Rosa Elefant"),
+ isCompatible = true,
+ )
+ db.getAppDao().insert(newApp)
+ assertGetAppListItems(db, "Rosa", 1)
+ assertGetAppListItems(db, "Elefant", 1)
+ }
+ }
+
+ private fun assertSearch(db: SupportSQLiteDatabase, query: String, expected: Int) {
+ db.query("SELECT * FROM AppMetadataFts WHERE AppMetadataFts MATCH '$query'").use { cursor ->
+ assertEquals(expected, cursor.count)
+ }
+ }
+
+ private fun assertGetAppListItems(db: FDroidDatabaseInt, query: String, expected: Int) {
+ db.getAppDao().getAppListItems(query).getOrFail().let { result ->
+ assertEquals(expected, result.size, "${result.map { it.name }}")
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt
index 815db1845..c5bd9fcde 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/FtsCaseInsensitiveMigrationTest.kt
@@ -11,193 +11,188 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.random.Random
+import kotlin.test.assertEquals
import kotlinx.coroutines.runBlocking
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.test.TestUtils.getRandomString
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
private const val TEST_DB = "fts-test"
@RunWith(AndroidJUnit4::class)
internal class FtsCaseInsensitiveMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- instrumentation = InstrumentationRegistry.getInstrumentation(),
- databaseClass = FDroidDatabaseInt::class.java,
- specs = emptyList(),
- openFactory = FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ instrumentation = InstrumentationRegistry.getInstrumentation(),
+ databaseClass = FDroidDatabaseInt::class.java,
+ specs = emptyList(),
+ openFactory = FrameworkSQLiteOpenHelperFactory(),
)
- @get:Rule
- val instantExec = InstantTaskExecutorRule()
+ @get:Rule val instantExec = InstantTaskExecutorRule()
- private val context: Context = ApplicationProvider.getApplicationContext()
+ private val context: Context = ApplicationProvider.getApplicationContext()
- private val repo = ContentValues().apply {
- put("repoId", 1)
- put("name", Converters.localizedTextV2toString(mapOf("de" to "a", "en-US" to "b")))
- put("address", getRandomString())
- put("certificate", "abcdef")
- put("description", Converters.localizedTextV2toString(mapOf("de" to "aa", "en-US" to "bb")))
- put("version", Random.nextLong())
- put("timestamp", Random.nextLong())
+ private val repo =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("name", Converters.localizedTextV2toString(mapOf("de" to "a", "en-US" to "b")))
+ put("address", getRandomString())
+ put("certificate", "abcdef")
+ put("description", Converters.localizedTextV2toString(mapOf("de" to "aa", "en-US" to "bb")))
+ put("version", Random.nextLong())
+ put("timestamp", Random.nextLong())
}
- private val repoPrefs = ContentValues().apply {
- put("repoId", 1)
- put("enabled", true)
- put("weight", 1)
+ private val repoPrefs =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("enabled", true)
+ put("weight", 1)
}
- private val oeffiMetadata = ContentValues().apply {
- put("packageName", "de.schildbach.oeffi")
- put("repoId", 1)
- put(
- "name", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffi", "en-US" to "Offi"
- )
- )
- )
- put(
- "description", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
- )
- )
- )
- put("license", "GPL-3.0")
- put(
- "summary", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Der König des Fahrplandschungels!",
- "en-US" to " King of public transit planning!"
- )
- )
- )
- put("localizedName", "Öffi")
- put("localizedSummary", "Der König des Fahrplandschungels!")
- put("added", Random.nextLong())
- put("lastUpdated", Random.nextLong())
- put("isCompatible", true)
+ private val oeffiMetadata =
+ ContentValues().apply {
+ put("packageName", "de.schildbach.oeffi")
+ put("repoId", 1)
+ put("name", Converters.localizedTextV2toString(mapOf("de" to "Öffi", "en-US" to "Offi")))
+ put(
+ "description",
+ Converters.localizedTextV2toString(
+ mapOf("de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")
+ ),
+ )
+ put("license", "GPL-3.0")
+ put(
+ "summary",
+ Converters.localizedTextV2toString(
+ mapOf(
+ "de" to "Der König des Fahrplandschungels!",
+ "en-US" to " King of public transit planning!",
+ )
+ ),
+ )
+ put("localizedName", "Öffi")
+ put("localizedSummary", "Der König des Fahrplandschungels!")
+ put("added", Random.nextLong())
+ put("lastUpdated", Random.nextLong())
+ put("isCompatible", true)
}
- private val oeffiVersion = ContentValues().apply {
- put("repoId", 1)
- put("packageName", "de.schildbach.oeffi")
- put("versionId", 1)
- put("added", 1000)
- put("isCompatible", true)
- put("file_name", "transportr.apk")
- put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
- put("manifest_versionName", "1.0.0")
- put("manifest_versionCode", 1)
+ private val oeffiVersion =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("packageName", "de.schildbach.oeffi")
+ put("versionId", 1)
+ put("added", 1000)
+ put("isCompatible", true)
+ put("file_name", "transportr.apk")
+ put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
+ put("manifest_versionName", "1.0.0")
+ put("manifest_versionCode", 1)
}
- private val transportrMetadata = ContentValues().apply {
- put("packageName", "de.grobox.liberario")
- put("repoId", 1)
- put(
- "name", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Transportr", "en-US" to "Transportr"
- )
- )
- )
- put(
- "description", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport"
- )
- )
- )
- put("license", "GPL-3.0")
- put(
- "summary", Converters.localizedTextV2toString(
- mapOf(
- "de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung",
- "en-US" to "Free Public Transport Assistant without Ads or Tracking"
- )
- )
- )
- put("localizedName", "Transportr")
- put(
- "localizedSummary",
- "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking"
- )
- put("added", Random.nextLong())
- put("lastUpdated", Random.nextLong())
- put("isCompatible", true)
+ private val transportrMetadata =
+ ContentValues().apply {
+ put("packageName", "de.grobox.liberario")
+ put("repoId", 1)
+ put(
+ "name",
+ Converters.localizedTextV2toString(mapOf("de" to "Transportr", "en-US" to "Transportr")),
+ )
+ put(
+ "description",
+ Converters.localizedTextV2toString(
+ mapOf("de" to "Öffentlicher Nahverkehr", "en-US" to "Public Transport")
+ ),
+ )
+ put("license", "GPL-3.0")
+ put(
+ "summary",
+ Converters.localizedTextV2toString(
+ mapOf(
+ "de" to "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung",
+ "en-US" to "Free Public Transport Assistant without Ads or Tracking",
+ )
+ ),
+ )
+ put("localizedName", "Transportr")
+ put(
+ "localizedSummary",
+ "Freier Assistent für den öffentlichen Nahverkehr ohne Werbung und Tracking",
+ )
+ put("added", Random.nextLong())
+ put("lastUpdated", Random.nextLong())
+ put("isCompatible", true)
}
- private val transportrVersion = ContentValues().apply {
- put("repoId", 1)
- put("packageName", "de.grobox.liberario")
- put("versionId", 1)
- put("added", 1000)
- put("isCompatible", true)
- put("file_name", "transportr.apk")
- put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
- put("manifest_versionName", "1.0.0")
- put("manifest_versionCode", 1)
+ private val transportrVersion =
+ ContentValues().apply {
+ put("repoId", 1)
+ put("packageName", "de.grobox.liberario")
+ put("versionId", 1)
+ put("added", 1000)
+ put("isCompatible", true)
+ put("file_name", "transportr.apk")
+ put("file_sha256", "73475CB40A568E8DA8A045CED110137E159F890AC4DA883B6B17DC651B3A8049")
+ put("manifest_versionName", "1.0.0")
+ put("manifest_versionCode", 1)
}
- @Test
- fun testMigration() = runBlocking {
- helper.createDatabase(TEST_DB, 5).use { db ->
- // Database has schema version 5. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- db.insert(CoreRepository.TABLE, SQLiteDatabase.CONFLICT_FAIL, repo)
- db.insert(RepositoryPreferences.TABLE, SQLiteDatabase.CONFLICT_FAIL, repoPrefs)
- db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiMetadata)
- db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrMetadata)
- db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiVersion)
- db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrVersion)
+ @Test
+ fun testMigration() = runBlocking {
+ helper.createDatabase(TEST_DB, 5).use { db ->
+ // Database has schema version 5. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ db.insert(CoreRepository.TABLE, SQLiteDatabase.CONFLICT_FAIL, repo)
+ db.insert(RepositoryPreferences.TABLE, SQLiteDatabase.CONFLICT_FAIL, repoPrefs)
+ db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiMetadata)
+ db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrMetadata)
+ db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiVersion)
+ db.insert(Version.TABLE, SQLiteDatabase.CONFLICT_FAIL, transportrVersion)
- // Show that search is case sensitive for diacritics
- assertSearch(db, "Öffi", 1)
- assertSearch(db, "öffi", 0)
- // using no diacritics does match any case
- assertSearch(db, "Offi", 0)
- assertSearch(db, "offi", 0)
- // it's case sensitive so only "öffentlichen" from Transportr is found
- assertSearch(db, "öff*", 1)
- assertSearch(db, "König", 1)
-
- }
-
- helper.runMigrationsAndValidate(TEST_DB, 6, true, MIGRATION_5_6).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- Room.databaseBuilder(context, FDroidDatabaseInt::class.java, TEST_DB)
- .allowMainThreadQueries()
- .addMigrations(MIGRATION_8_9) // was added later
- .build().use { db ->
- // assert that apps are still there
- val metadata = db.getAppDao().getAppMetadata()
- assertEquals(2, metadata.size)
- // default search with no diacritics
- assertGetAppListItems(db, "*Transport*", 2)
-
- // other tests here were removed, because MIGRATION_8_9 changed this once again
- // and has its own test
- }
+ // Show that search is case sensitive for diacritics
+ assertSearch(db, "Öffi", 1)
+ assertSearch(db, "öffi", 0)
+ // using no diacritics does match any case
+ assertSearch(db, "Offi", 0)
+ assertSearch(db, "offi", 0)
+ // it's case sensitive so only "öffentlichen" from Transportr is found
+ assertSearch(db, "öff*", 1)
+ assertSearch(db, "König", 1)
}
- private fun assertSearch(db: SupportSQLiteDatabase, query: String, expected: Int) {
- db.query("SELECT * FROM AppMetadataFts WHERE AppMetadataFts MATCH '$query'")
- .use { cursor -> assertEquals(expected, cursor.count) }
- }
+ helper.runMigrationsAndValidate(TEST_DB, 6, true, MIGRATION_5_6).close()
- private fun assertGetAppListItems(db: FDroidDatabaseInt, query: String, expected: Int) {
- db.getAppDao().getAppListItems(query).getOrFail().let {
- assertEquals(expected, it.size)
- }
- }
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ Room.databaseBuilder(context, FDroidDatabaseInt::class.java, TEST_DB)
+ .allowMainThreadQueries()
+ .addMigrations(MIGRATION_8_9) // was added later
+ .build()
+ .use { db ->
+ // assert that apps are still there
+ val metadata = db.getAppDao().getAppMetadata()
+ assertEquals(2, metadata.size)
+ // default search with no diacritics
+ assertGetAppListItems(db, "*Transport*", 2)
+ // other tests here were removed, because MIGRATION_8_9 changed this once again
+ // and has its own test
+ }
+ }
+
+ private fun assertSearch(db: SupportSQLiteDatabase, query: String, expected: Int) {
+ db.query("SELECT * FROM AppMetadataFts WHERE AppMetadataFts MATCH '$query'").use { cursor ->
+ assertEquals(expected, cursor.count)
+ }
+ }
+
+ private fun assertGetAppListItems(db: FDroidDatabaseInt, query: String, expected: Int) {
+ db.getAppDao().getAppListItems(query).getOrFail().let { assertEquals(expected, it.size) }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt
index 0f2c515c3..093f83f84 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/IndexV1InsertTest.kt
@@ -1,6 +1,9 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
import kotlinx.serialization.SerializationException
import org.apache.commons.io.input.CountingInputStream
import org.fdroid.index.IndexConverter
@@ -16,119 +19,109 @@ import org.fdroid.test.TestDataEmptyV1
import org.fdroid.test.TestDataMaxV1
import org.fdroid.test.TestDataMidV1
import org.fdroid.test.TestDataMinV1
+import org.fdroid.test.TestUtils.getRes
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
internal class IndexV1InsertTest : DbTest() {
- private val indexConverter = IndexConverter()
+ private val indexConverter = IndexConverter()
- @Test
- fun testStreamEmptyIntoDb() {
- val repoId = streamIndex("index-empty-v1.json")
- assertEquals(1, repoDao.getRepositories().size)
- val index = indexConverter.toIndexV2(TestDataEmptyV1.index)
- assertDbEquals(repoId, index)
+ @Test
+ fun testStreamEmptyIntoDb() {
+ val repoId = streamIndex("index-empty-v1.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ val index = indexConverter.toIndexV2(TestDataEmptyV1.index)
+ assertDbEquals(repoId, index)
+ }
+
+ @Test
+ fun testStreamMinIntoDb() {
+ val repoId = streamIndex("index-min-v1.json")
+ assertTrue(repoDao.getRepositories().size == 1)
+ val index = indexConverter.toIndexV2(TestDataMinV1.index)
+ assertDbEquals(repoId, index)
+ }
+
+ @Test
+ fun testStreamMidIntoDb() {
+ val repoId = streamIndex("index-mid-v1.json")
+ assertTrue(repoDao.getRepositories().size == 1)
+ val index = indexConverter.toIndexV2(TestDataMidV1.index)
+ assertDbEquals(repoId, index)
+ }
+
+ @Test
+ fun testStreamMaxIntoDb() {
+ val repoId = streamIndex("index-max-v1.json")
+ assertTrue(repoDao.getRepositories().size == 1)
+ val index = indexConverter.toIndexV2(TestDataMaxV1.index)
+ assertDbEquals(repoId, index)
+ }
+
+ private fun streamIndex(path: String): Long {
+ val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo")
+ val streamReceiver = TestStreamReceiver(repoId)
+ val indexProcessor = IndexV1StreamProcessor(streamReceiver, -1)
+ db.runInTransaction {
+ getRes(path).use { indexStream -> indexProcessor.process(indexStream) }
}
+ return repoId
+ }
- @Test
- fun testStreamMinIntoDb() {
- val repoId = streamIndex("index-min-v1.json")
- assertTrue(repoDao.getRepositories().size == 1)
- val index = indexConverter.toIndexV2(TestDataMinV1.index)
- assertDbEquals(repoId, index)
- }
-
- @Test
- fun testStreamMidIntoDb() {
- val repoId = streamIndex("index-mid-v1.json")
- assertTrue(repoDao.getRepositories().size == 1)
- val index = indexConverter.toIndexV2(TestDataMidV1.index)
- assertDbEquals(repoId, index)
- }
-
- @Test
- fun testStreamMaxIntoDb() {
- val repoId = streamIndex("index-max-v1.json")
- assertTrue(repoDao.getRepositories().size == 1)
- val index = indexConverter.toIndexV2(TestDataMaxV1.index)
- assertDbEquals(repoId, index)
- }
-
- private fun streamIndex(path: String): Long {
+ @Test
+ fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
+ val cIn = CountingInputStream(getRes("index-max-v1.json"))
+ assertFailsWith {
+ db.runInTransaction {
val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo")
- val streamReceiver = TestStreamReceiver(repoId)
+ val streamReceiver =
+ TestStreamReceiver(repoId) { if (cIn.byteCount > 0) throw SerializationException() }
val indexProcessor = IndexV1StreamProcessor(streamReceiver, -1)
- db.runInTransaction {
- assets.open(path).use { indexStream ->
- indexProcessor.process(indexStream)
- }
- }
- return repoId
+ cIn.use { indexStream -> indexProcessor.process(indexStream) }
+ }
+ }
+ assertTrue(repoDao.getRepositories().isEmpty())
+ assertTrue(appDao.countApps() == 0)
+ assertTrue(appDao.countLocalizedFiles() == 0)
+ assertTrue(appDao.countLocalizedFileLists() == 0)
+ assertTrue(versionDao.countAppVersions() == 0)
+ assertTrue(versionDao.countVersionedStrings() == 0)
+ }
+
+ @Suppress("DEPRECATION")
+ inner class TestStreamReceiver(repoId: Long, private val callback: () -> Unit = {}) :
+ IndexV1StreamReceiver {
+ private val streamReceiver = DbV1StreamReceiver(db, repoId) { true }
+
+ override fun receive(repo: RepoV2, version: Long) {
+ streamReceiver.receive(repo, version)
+ callback()
}
- @Test
- fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
- val cIn = CountingInputStream(assets.open("index-max-v1.json"))
- assertFailsWith {
- db.runInTransaction {
- val repoId = db.getRepositoryDao().insertEmptyRepo("https://f-droid.org/repo")
- val streamReceiver = TestStreamReceiver(repoId) {
- if (cIn.byteCount > 0) throw SerializationException()
- }
- val indexProcessor = IndexV1StreamProcessor(streamReceiver, -1)
- cIn.use { indexStream ->
- indexProcessor.process(indexStream)
- }
- }
- }
- assertTrue(repoDao.getRepositories().isEmpty())
- assertTrue(appDao.countApps() == 0)
- assertTrue(appDao.countLocalizedFiles() == 0)
- assertTrue(appDao.countLocalizedFileLists() == 0)
- assertTrue(versionDao.countAppVersions() == 0)
- assertTrue(versionDao.countVersionedStrings() == 0)
+ override fun receive(packageName: String, m: MetadataV2) {
+ streamReceiver.receive(packageName, m)
+ callback()
}
- @Suppress("DEPRECATION")
- inner class TestStreamReceiver(
- repoId: Long,
- private val callback: () -> Unit = {},
- ) : IndexV1StreamReceiver {
- private val streamReceiver = DbV1StreamReceiver(db, repoId) { true }
- override fun receive(repo: RepoV2, version: Long) {
- streamReceiver.receive(repo, version)
- callback()
- }
-
- override fun receive(packageName: String, m: MetadataV2) {
- streamReceiver.receive(packageName, m)
- callback()
- }
-
- override fun receive(packageName: String, v: Map) {
- streamReceiver.receive(packageName, v)
- callback()
- }
-
- override fun updateRepo(
- antiFeatures: Map,
- categories: Map,
- releaseChannels: Map,
- ) {
- streamReceiver.updateRepo(antiFeatures, categories, releaseChannels)
- callback()
- }
-
- override fun updateAppMetadata(packageName: String, preferredSigner: String?) {
- streamReceiver.updateAppMetadata(packageName, preferredSigner)
- callback()
- }
-
+ override fun receive(packageName: String, v: Map) {
+ streamReceiver.receive(packageName, v)
+ callback()
}
+ override fun updateRepo(
+ antiFeatures: Map,
+ categories: Map,
+ releaseChannels: Map,
+ ) {
+ streamReceiver.updateRepo(antiFeatures, categories, releaseChannels)
+ callback()
+ }
+
+ override fun updateAppMetadata(packageName: String, preferredSigner: String?) {
+ streamReceiver.updateAppMetadata(packageName, preferredSigner)
+ callback()
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2DiffTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2DiffTest.kt
index f0e4db3c7..695cd3c54 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2DiffTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2DiffTest.kt
@@ -1,6 +1,9 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import java.io.ByteArrayInputStream
+import java.io.InputStream
+import kotlin.test.assertFailsWith
import kotlinx.serialization.SerializationException
import org.fdroid.index.IndexParser
import org.fdroid.index.parseV2
@@ -11,204 +14,225 @@ import org.fdroid.test.TestDataMaxV2.PACKAGE_NAME_3
import org.fdroid.test.TestDataMaxV2.app3
import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
+import org.fdroid.test.TestUtils.getRes
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
-import java.io.ByteArrayInputStream
-import java.io.InputStream
-import kotlin.test.assertFailsWith
@RunWith(AndroidJUnit4::class)
internal class IndexV2DiffTest : DbTest() {
- @Test
- @Ignore("use for testing specific index on demand")
- fun testBrokenIndexDiff() {
- val endPath = "tmp/index-end.json"
- val endIndex = IndexParser.parseV2(assets.open(endPath))
- testDiff(
- startPath = "tmp/index-start.json",
- diffPath = "tmp/diff.json",
- endIndex = endIndex,
- )
- }
+ @Test
+ @Ignore("use for testing specific index on demand")
+ fun testBrokenIndexDiff() {
+ val endPath = "tmp/index-end.json"
+ val endIndex = IndexParser.parseV2(getRes(endPath))
+ testDiff(startPath = "tmp/index-start.json", diffPath = "tmp/diff.json", endIndex = endIndex)
+ }
- @Test
- fun testEmptyToMin() = testDiff(
- startPath = "index-empty-v2.json",
- diffPath = "diff-empty-min/23.json",
- endIndex = TestDataMinV2.index,
+ @Test
+ fun testEmptyToMin() =
+ testDiff(
+ startPath = "index-empty-v2.json",
+ diffPath = "diff-empty-min/23.json",
+ endIndex = TestDataMinV2.index,
)
- @Test
- fun testEmptyToMid() = testDiff(
- startPath = "index-empty-v2.json",
- diffPath = "diff-empty-mid/23.json",
- endIndex = TestDataMidV2.index,
+ @Test
+ fun testEmptyToMid() =
+ testDiff(
+ startPath = "index-empty-v2.json",
+ diffPath = "diff-empty-mid/23.json",
+ endIndex = TestDataMidV2.index,
)
- @Test
- fun testEmptyToMax() = testDiff(
- startPath = "index-empty-v2.json",
- diffPath = "diff-empty-max/23.json",
- endIndex = TestDataMaxV2.index,
+ @Test
+ fun testEmptyToMax() =
+ testDiff(
+ startPath = "index-empty-v2.json",
+ diffPath = "diff-empty-max/23.json",
+ endIndex = TestDataMaxV2.index,
)
- @Test
- fun testMinToMid() = testDiff(
- startPath = "index-min-v2.json",
- diffPath = "diff-empty-mid/42.json",
- endIndex = TestDataMidV2.index,
+ @Test
+ fun testMinToMid() =
+ testDiff(
+ startPath = "index-min-v2.json",
+ diffPath = "diff-empty-mid/42.json",
+ endIndex = TestDataMidV2.index,
)
- @Test
- fun testMinToMax() = testDiff(
- startPath = "index-min-v2.json",
- diffPath = "diff-empty-max/42.json",
- endIndex = TestDataMaxV2.index,
+ @Test
+ fun testMinToMax() =
+ testDiff(
+ startPath = "index-min-v2.json",
+ diffPath = "diff-empty-max/42.json",
+ endIndex = TestDataMaxV2.index,
)
- @Test
- fun testMidToMax() = testDiff(
- startPath = "index-mid-v2.json",
- diffPath = "diff-empty-max/1337.json",
- endIndex = TestDataMaxV2.index,
+ @Test
+ fun testMidToMax() =
+ testDiff(
+ startPath = "index-mid-v2.json",
+ diffPath = "diff-empty-max/1337.json",
+ endIndex = TestDataMaxV2.index,
)
- @Test
- fun testMinRemoveApp() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": null
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(packages = emptyMap()),
- )
- }
-
- @Test
- fun testMinNoMetadataRemoveVersion() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": {
- "added": 0
- },
- "versions": {
- "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": null
+ @Test
+ fun testMinRemoveApp() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": null
+ }
}
- }
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = TestDataMinV2.index.packages.mapValues {
- it.value.copy(versions = emptyMap())
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex = TestDataMinV2.index.copy(packages = emptyMap()),
+ )
+ }
+
+ @Test
+ fun testMinNoMetadataRemoveVersion() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": {
+ "added": 0
+ },
+ "versions": {
+ "824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": null
+ }
+ }
}
- ),
- )
- }
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex =
+ TestDataMinV2.index.copy(
+ packages = TestDataMinV2.index.packages.mapValues { it.value.copy(versions = emptyMap()) }
+ ),
+ )
+ }
- @Test
- fun testMinNoVersionsUnknownKey() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": {
- "added": 42
- },
- "unknownKey": "should get ignored"
- }
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = TestDataMinV2.index.packages.mapValues {
- it.value.copy(metadata = it.value.metadata.copy(added = 42))
+ @Test
+ fun testMinNoVersionsUnknownKey() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": {
+ "added": 42
+ },
+ "unknownKey": "should get ignored"
+ }
}
- ),
- )
- }
-
- @Test
- fun testMinRemoveMetadata() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": null
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex =
+ TestDataMinV2.index.copy(
+ packages =
+ TestDataMinV2.index.packages.mapValues {
+ it.value.copy(metadata = it.value.metadata.copy(added = 42))
}
- },
- "unknownKey": "should get ignored"
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = emptyMap()
- ),
- )
- }
+ ),
+ )
+ }
- @Test
- fun testMinRemoveVersions() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- "versions": null
- }
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = TestDataMinV2.index.packages.mapValues {
- it.value.copy(versions = emptyMap())
+ @Test
+ fun testMinRemoveMetadata() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": null
+ }
+ },
+ "unknownKey": "should get ignored"
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex = TestDataMinV2.index.copy(packages = emptyMap()),
+ )
+ }
+
+ @Test
+ fun testMinRemoveVersions() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "versions": null
+ }
}
- ),
- )
- }
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex =
+ TestDataMinV2.index.copy(
+ packages = TestDataMinV2.index.packages.mapValues { it.value.copy(versions = emptyMap()) }
+ ),
+ )
+ }
- @Test
- @Ignore("Removing all packages via diff currently not supported") // TODO
- fun testMinRemovePackages() {
- val diffJson = """{
- "packages": null
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = emptyMap(),
- ),
- )
- }
+ @Test
+ @Ignore("Removing all packages via diff currently not supported") // TODO
+ fun testMinRemovePackages() {
+ val diffJson =
+ """
+ {
+ "packages": null
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex = TestDataMinV2.index.copy(packages = emptyMap()),
+ )
+ }
- @Test
- fun testMinNoMetadataNoVersion() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- }
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index,
- )
- }
+ @Test
+ fun testMinNoMetadataNoVersion() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ }
+ }
+ }
+ """
+ .trimIndent()
+ testJsonDiff(startPath = "index-min-v2.json", diff = diffJson, endIndex = TestDataMinV2.index)
+ }
- @Test
- fun testMaxRemoveOptionals() {
- val diffJson = """{
+ @Test
+ fun testMaxRemoveOptionals() {
+ val diffJson =
+ """{
"packages": {
"$PACKAGE_NAME_3": {
"metadata": {
@@ -247,111 +271,124 @@ internal class IndexV2DiffTest : DbTest() {
}
}
}
- }""".trimIndent()
- val packages = TestDataMaxV2.index.packages.toMutableMap()
- val versions = packages[PACKAGE_NAME_3]!!.versions.toMutableMap()
- val version = versions["8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06"]!!
- versions["8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06"] = version
- .copy(
- src = null,
- releaseChannels = emptyList(),
- antiFeatures = emptyMap(),
- whatsNew = emptyMap(),
- manifest = version.manifest.copy(
- usesSdk = null,
- maxSdkVersion = null,
- signer = null,
- usesPermission = emptyList(),
- usesPermissionSdk23 = emptyList(),
- nativecode = emptyList(),
- features = emptyList(),
- )
- )
- packages[PACKAGE_NAME_3] = app3.copy(
- metadata = app3.metadata.copy(
- name = null,
- summary = null,
- description = null,
- categories = emptyList(),
- donate = emptyList(),
- icon = null,
- featureGraphic = null,
- promoGraphic = null,
- tvBanner = null,
- video = null,
- screenshots = null,
- ),
- versions = versions,
- )
- testJsonDiff(
- startPath = "index-max-v2.json",
- diff = diffJson,
- endIndex = TestDataMaxV2.index.copy(packages = packages),
- )
- }
+ }"""
+ .trimIndent()
+ val packages = TestDataMaxV2.index.packages.toMutableMap()
+ val versions = packages[PACKAGE_NAME_3]!!.versions.toMutableMap()
+ val version = versions["8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06"]!!
+ versions["8c89ce2f42f4a89af8ca6e1ea220f9dfdee220724d8a9cc067d510ac6f3e0d06"] =
+ version.copy(
+ src = null,
+ releaseChannels = emptyList(),
+ antiFeatures = emptyMap(),
+ whatsNew = emptyMap(),
+ manifest =
+ version.manifest.copy(
+ usesSdk = null,
+ maxSdkVersion = null,
+ signer = null,
+ usesPermission = emptyList(),
+ usesPermissionSdk23 = emptyList(),
+ nativecode = emptyList(),
+ features = emptyList(),
+ ),
+ )
+ packages[PACKAGE_NAME_3] =
+ app3.copy(
+ metadata =
+ app3.metadata.copy(
+ name = null,
+ summary = null,
+ description = null,
+ categories = emptyList(),
+ donate = emptyList(),
+ icon = null,
+ featureGraphic = null,
+ promoGraphic = null,
+ tvBanner = null,
+ video = null,
+ screenshots = null,
+ ),
+ versions = versions,
+ )
+ testJsonDiff(
+ startPath = "index-max-v2.json",
+ diff = diffJson,
+ endIndex = TestDataMaxV2.index.copy(packages = packages),
+ )
+ }
- @Test
- fun testAppDenyKeyList() {
- val diffRepoIdJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": {
- "repoId": 1
+ @Test
+ fun testAppDenyKeyList() {
+ val diffRepoIdJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": {
+ "repoId": 1
+ }
+ }
+ }
}
- }
- }
- }""".trimIndent()
- assertFailsWith {
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffRepoIdJson,
- endIndex = TestDataMinV2.index,
- )
- }
- val diffPackageNameJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": {
- "packageName": "foo"
+ """
+ .trimIndent()
+ assertFailsWith {
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffRepoIdJson,
+ endIndex = TestDataMinV2.index,
+ )
+ }
+ val diffPackageNameJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": {
+ "packageName": "foo"
+ }
+ }
+ }
}
- }
- }
- }""".trimIndent()
- assertFailsWith {
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffPackageNameJson,
- endIndex = TestDataMinV2.index,
- )
- }
+ """
+ .trimIndent()
+ assertFailsWith {
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffPackageNameJson,
+ endIndex = TestDataMinV2.index,
+ )
}
+ }
- @Test
- fun testVersionsDenyKeyList() {
- assertFailsWith {
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = getMinVersionJson(""""packageName": "foo""""),
- endIndex = TestDataMinV2.index,
- )
- }
- assertFailsWith {
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = getMinVersionJson(""""repoId": 1"""),
- endIndex = TestDataMinV2.index,
- )
- }
- assertFailsWith {
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = getMinVersionJson(""""versionId": "bar""""),
- endIndex = TestDataMinV2.index,
- )
- }
+ @Test
+ fun testVersionsDenyKeyList() {
+ assertFailsWith {
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = getMinVersionJson(""""packageName": "foo""""),
+ endIndex = TestDataMinV2.index,
+ )
}
+ assertFailsWith {
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = getMinVersionJson(""""repoId": 1"""),
+ endIndex = TestDataMinV2.index,
+ )
+ }
+ assertFailsWith {
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = getMinVersionJson(""""versionId": "bar""""),
+ endIndex = TestDataMinV2.index,
+ )
+ }
+ }
- private fun getMinVersionJson(insert: String) = """{
+ private fun getMinVersionJson(insert: String) =
+ """{
"packages": {
"org.fdroid.min1": {
"versions": {
@@ -360,87 +397,97 @@ internal class IndexV2DiffTest : DbTest() {
}
}
}
- }""".trimIndent()
+ }"""
+ .trimIndent()
- @Test
- fun testMidRemoveScreenshots() {
- val diffRepoIdJson = """{
- "packages": {
- "org.fdroid.fdroid": {
- "metadata": {
- "screenshots": null
- }
- }
- }
- }""".trimIndent()
- val fdroidPackage = TestDataMidV2.packages["org.fdroid.fdroid"]!!.copy(
- metadata = TestDataMidV2.packages["org.fdroid.fdroid"]!!.metadata.copy(
- screenshots = null,
- )
- )
- testJsonDiff(
- startPath = "index-mid-v2.json",
- diff = diffRepoIdJson,
- endIndex = TestDataMidV2.index.copy(
- packages = mapOf(
- TestDataMidV2.PACKAGE_NAME_1 to TestDataMidV2.app1,
- TestDataMidV2.PACKAGE_NAME_2 to fdroidPackage,
- )
- ),
- )
- }
-
- @Test
- fun testMinAddChinese() {
- val diffJson = """{
- "packages": {
- "org.fdroid.min1": {
- "metadata": {
- "name": { "zh-CN": "自由软件仓库" },
- "summary": { "ja": "这个仓库ä¸çš„" },
- "description": { "ko-KR": "切始终是从" }
- }
- }
- }
- }""".trimIndent()
- testJsonDiff(
- startPath = "index-min-v2.json",
- diff = diffJson,
- endIndex = TestDataMinV2.index.copy(
- packages = TestDataMinV2.index.packages.mapValues {
- it.value.copy(
- metadata = it.value.metadata.copy(
- // zero whitespaces (to separate tokens) will be added in testJsonDiff()
- name = mapOf("zh-CN" to "自由软件仓库"),
- summary = mapOf("ja" to "这个仓库ä¸çš„"),
- description = mapOf("ko-KR" to "切始终是从"),
- )
- )
+ @Test
+ fun testMidRemoveScreenshots() {
+ val diffRepoIdJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.fdroid": {
+ "metadata": {
+ "screenshots": null
+ }
+ }
}
- ),
- )
- }
+ }
+ """
+ .trimIndent()
+ val fdroidPackage =
+ TestDataMidV2.packages["org.fdroid.fdroid"]!!.copy(
+ metadata = TestDataMidV2.packages["org.fdroid.fdroid"]!!.metadata.copy(screenshots = null)
+ )
+ testJsonDiff(
+ startPath = "index-mid-v2.json",
+ diff = diffRepoIdJson,
+ endIndex =
+ TestDataMidV2.index.copy(
+ packages =
+ mapOf(
+ TestDataMidV2.PACKAGE_NAME_1 to TestDataMidV2.app1,
+ TestDataMidV2.PACKAGE_NAME_2 to fdroidPackage,
+ )
+ ),
+ )
+ }
- private fun testJsonDiff(startPath: String, diff: String, endIndex: IndexV2) {
- testDiff(startPath, ByteArrayInputStream(diff.toByteArray()), endIndex)
- }
+ @Test
+ fun testMinAddChinese() {
+ val diffJson =
+ """
+ {
+ "packages": {
+ "org.fdroid.min1": {
+ "metadata": {
+ "name": { "zh-CN": "自由软件仓库" },
+ "summary": { "ja": "这个仓库ä¸çš„" },
+ "description": { "ko-KR": "切始终是从" }
+ }
+ }
+ }
+ }
+ """
+ .trimIndent()
+ testJsonDiff(
+ startPath = "index-min-v2.json",
+ diff = diffJson,
+ endIndex =
+ TestDataMinV2.index.copy(
+ packages =
+ TestDataMinV2.index.packages.mapValues {
+ it.value.copy(
+ metadata =
+ it.value.metadata.copy(
+ // zero whitespaces (to separate tokens) will be added in testJsonDiff()
+ name = mapOf("zh-CN" to "自由软件仓库"),
+ summary = mapOf("ja" to "这个仓库ä¸çš„"),
+ description = mapOf("ko-KR" to "切始终是从"),
+ )
+ )
+ }
+ ),
+ )
+ }
- private fun testDiff(startPath: String, diffPath: String, endIndex: IndexV2) {
- testDiff(startPath, assets.open(diffPath), endIndex)
- }
+ private fun testJsonDiff(startPath: String, diff: String, endIndex: IndexV2) {
+ testDiff(startPath, ByteArrayInputStream(diff.toByteArray()), endIndex)
+ }
- private fun testDiff(startPath: String, diffStream: InputStream, endIndex: IndexV2) {
- // stream start index into the DB
- val repoId = streamIndexV2IntoDb(startPath)
+ private fun testDiff(startPath: String, diffPath: String, endIndex: IndexV2) {
+ testDiff(startPath, getRes(diffPath), endIndex)
+ }
- // apply diff stream to the DB
- val streamReceiver = DbV2DiffStreamReceiver(db, repoId) { true }
- val streamProcessor = IndexV2DiffStreamProcessor(streamReceiver)
- db.runInTransaction {
- streamProcessor.process(42, diffStream) {}
- }
- // assert that changed DB data is equal to given endIndex
- assertDbEquals(repoId, endIndex)
- }
+ private fun testDiff(startPath: String, diffStream: InputStream, endIndex: IndexV2) {
+ // stream start index into the DB
+ val repoId = streamIndexV2IntoDb(startPath)
+ // apply diff stream to the DB
+ val streamReceiver = DbV2DiffStreamReceiver(db, repoId) { true }
+ val streamProcessor = IndexV2DiffStreamProcessor(streamReceiver)
+ db.runInTransaction { streamProcessor.process(42, diffStream) {} }
+ // assert that changed DB data is equal to given endIndex
+ assertDbEquals(repoId, endIndex)
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt
index fffc872f6..0a4df0628 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/IndexV2InsertTest.kt
@@ -1,6 +1,9 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertTrue
import kotlinx.serialization.SerializationException
import org.apache.commons.io.input.CountingInputStream
import org.fdroid.CompatibilityChecker
@@ -9,73 +12,68 @@ import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
+import org.fdroid.test.TestUtils.getRes
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
internal class IndexV2InsertTest : DbTest() {
- @Test
- fun testStreamEmptyIntoDb() {
- val repoId = streamIndexV2IntoDb("index-empty-v2.json")
- assertEquals(1, repoDao.getRepositories().size)
- assertDbEquals(repoId, TestDataEmptyV2.index)
- }
+ @Test
+ fun testStreamEmptyIntoDb() {
+ val repoId = streamIndexV2IntoDb("index-empty-v2.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ assertDbEquals(repoId, TestDataEmptyV2.index)
+ }
- @Test
- fun testStreamMinIntoDb() {
- val repoId = streamIndexV2IntoDb("index-min-v2.json")
- assertEquals(1, repoDao.getRepositories().size)
- assertDbEquals(repoId, TestDataMinV2.index)
- }
+ @Test
+ fun testStreamMinIntoDb() {
+ val repoId = streamIndexV2IntoDb("index-min-v2.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ assertDbEquals(repoId, TestDataMinV2.index)
+ }
- @Test
- fun testStreamMinReorderedIntoDb() {
- val repoId = streamIndexV2IntoDb("index-min-reordered-v2.json")
- assertEquals(1, repoDao.getRepositories().size)
- assertDbEquals(repoId, TestDataMinV2.index)
- }
+ @Test
+ fun testStreamMinReorderedIntoDb() {
+ val repoId = streamIndexV2IntoDb("index-min-reordered-v2.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ assertDbEquals(repoId, TestDataMinV2.index)
+ }
- @Test
- fun testStreamMidIntoDb() {
- val repoId = streamIndexV2IntoDb("index-mid-v2.json")
- assertEquals(1, repoDao.getRepositories().size)
- assertDbEquals(repoId, TestDataMidV2.index)
- }
+ @Test
+ fun testStreamMidIntoDb() {
+ val repoId = streamIndexV2IntoDb("index-mid-v2.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ assertDbEquals(repoId, TestDataMidV2.index)
+ }
- @Test
- fun testStreamMaxIntoDb() {
- val repoId = streamIndexV2IntoDb("index-max-v2.json")
- assertEquals(1, repoDao.getRepositories().size)
- assertDbEquals(repoId, TestDataMaxV2.index)
- }
+ @Test
+ fun testStreamMaxIntoDb() {
+ val repoId = streamIndexV2IntoDb("index-max-v2.json")
+ assertEquals(1, repoDao.getRepositories().size)
+ assertDbEquals(repoId, TestDataMaxV2.index)
+ }
- @Test
- fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
- val cIn = CountingInputStream(assets.open("index-max-v2.json"))
- val compatibilityChecker = CompatibilityChecker {
- if (cIn.byteCount > 0) throw SerializationException()
- true
- }
- assertFailsWith {
- db.runInTransaction {
- val repoId = db.getRepositoryDao().insertEmptyRepo("http://example.org")
- val streamReceiver = DbV2StreamReceiver(db, repoId, compatibilityChecker)
- val indexProcessor = IndexV2FullStreamProcessor(streamReceiver)
- cIn.use { indexStream ->
- indexProcessor.process(42, indexStream) {}
- }
- }
- }
- assertTrue(repoDao.getRepositories().isEmpty())
- assertTrue(appDao.countApps() == 0)
- assertTrue(appDao.countLocalizedFiles() == 0)
- assertTrue(appDao.countLocalizedFileLists() == 0)
- assertTrue(versionDao.countAppVersions() == 0)
- assertTrue(versionDao.countVersionedStrings() == 0)
+ @Test
+ fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
+ val cIn = CountingInputStream(getRes("index-max-v2.json"))
+ val compatibilityChecker = CompatibilityChecker {
+ if (cIn.byteCount > 0) throw SerializationException()
+ true
}
-
+ assertFailsWith {
+ db.runInTransaction {
+ val repoId = db.getRepositoryDao().insertEmptyRepo("http://example.org")
+ val streamReceiver = DbV2StreamReceiver(db, repoId, compatibilityChecker)
+ val indexProcessor = IndexV2FullStreamProcessor(streamReceiver)
+ cIn.use { indexStream -> indexProcessor.process(42, indexStream) {} }
+ }
+ }
+ assertTrue(repoDao.getRepositories().isEmpty())
+ assertTrue(appDao.countApps() == 0)
+ assertTrue(appDao.countLocalizedFiles() == 0)
+ assertTrue(appDao.countLocalizedFileLists() == 0)
+ assertTrue(versionDao.countAppVersions() == 0)
+ assertTrue(versionDao.countVersionedStrings() == 0)
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt
index 0a4f171ff..6a6c47113 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/MultiRepoMigrationTest.kt
@@ -8,313 +8,367 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import org.fdroid.database.Converters.localizedTextV2toString
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
+import org.fdroid.database.Converters.localizedTextV2toString
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
private const val TEST_DB = "migration-test"
@RunWith(AndroidJUnit4::class)
internal class MultiRepoMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- InstrumentationRegistry.getInstrumentation(),
- FDroidDatabaseInt::class.java,
- listOf(MultiRepoMigration()),
- FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ InstrumentationRegistry.getInstrumentation(),
+ FDroidDatabaseInt::class.java,
+ listOf(MultiRepoMigration()),
+ FrameworkSQLiteOpenHelperFactory(),
)
- private val fdroidArchiveRepo = InitialRepository(
- name = "F-Droid Archive",
- address = "https://f-droid.org/archive",
- description = "The archive repository of the F-Droid client. " +
- "This contains older versions of\n" +
- "applications from the main repository.",
- certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
- version = 13L,
- enabled = false,
- weight = 0, // gets set later
+ private val fdroidArchiveRepo =
+ InitialRepository(
+ name = "F-Droid Archive",
+ address = "https://f-droid.org/archive",
+ description =
+ "The archive repository of the F-Droid client. " +
+ "This contains older versions of\n" +
+ "applications from the main repository.",
+ certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
+ version = 13L,
+ enabled = false,
+ weight = 0, // gets set later
)
- private val fdroidRepo = InitialRepository(
- name = "F-Droid",
- address = "https://f-droid.org/repo",
- description = "The official F-Droid Free Software repository. " +
- "Everything in this repository is always built from the source code.",
- certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
- version = 13L,
- enabled = true,
- weight = 0, // gets set later
+ private val fdroidRepo =
+ InitialRepository(
+ name = "F-Droid",
+ address = "https://f-droid.org/repo",
+ description =
+ "The official F-Droid Free Software repository. " +
+ "Everything in this repository is always built from the source code.",
+ certificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d010105",
+ version = 13L,
+ enabled = true,
+ weight = 0, // gets set later
)
- private val guardianArchiveRepo = InitialRepository(
- name = "Guardian Project Archive",
- address = "https://guardianproject.info/fdroid/archive",
- description = "The official repository of The Guardian Project apps" +
- " for use with F-Droid client. This\n" +
- " contains older versions of applications from the main repository.\n",
- certificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d010105",
- version = 13L,
- enabled = false,
- weight = 0, // gets set later
+ private val guardianArchiveRepo =
+ InitialRepository(
+ name = "Guardian Project Archive",
+ address = "https://guardianproject.info/fdroid/archive",
+ description =
+ "The official repository of The Guardian Project apps" +
+ " for use with F-Droid client. This\n" +
+ " contains older versions of applications from the main repository.\n",
+ certificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d010105",
+ version = 13L,
+ enabled = false,
+ weight = 0, // gets set later
)
- private val guardianRepo = InitialRepository(
- name = "Guardian Project",
- address = "https://guardianproject.info/fdroid/repo",
- description = "The official app repository of The Guardian Project. " +
- "Applications in this repository\n" +
- " are official binaries build by the original application developers " +
- "and signed by the\n" +
- " same key as the APKs that are released in the Google Play store.",
- certificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d010105",
- version = 13L,
- enabled = false,
- weight = 0, // gets set later
+ private val guardianRepo =
+ InitialRepository(
+ name = "Guardian Project",
+ address = "https://guardianproject.info/fdroid/repo",
+ description =
+ "The official app repository of The Guardian Project. " +
+ "Applications in this repository\n" +
+ " are official binaries build by the original application developers " +
+ "and signed by the\n" +
+ " same key as the APKs that are released in the Google Play store.",
+ certificate = "308205d8308203c0020900a397b4da7ecda034300d06092a864886f70d010105",
+ version = 13L,
+ enabled = false,
+ weight = 0, // gets set later
)
- @Test
- fun migrateDefaultRepos() {
- val reposToMigrate = listOf(
- fdroidArchiveRepo.copy(weight = 1),
- fdroidRepo.copy(weight = 2),
+ @Test
+ fun migrateDefaultRepos() {
+ val reposToMigrate = listOf(fdroidArchiveRepo.copy(weight = 1), fdroidRepo.copy(weight = 2))
+ runRepoMigration(reposToMigrate) { db ->
+ db
+ .getRepositoryDao()
+ .getRepositories()
+ .sortedByDescending { it.weight }
+ .also { repos ->
+ assertEquals(reposToMigrate.size, repos.size)
+ assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
+ assertEquals(fdroidRepo.address, repos[0].address)
+ assertEquals(1_000_000_000, repos[0].weight)
+ assertEquals(fdroidArchiveRepo.address, repos[1].address)
+ assertEquals(999_999_999, repos[1].weight)
+ }
+ }
+ }
+
+ @Test
+ fun migrateOldDefaultRepos() {
+ val reposToMigrate =
+ listOf(
+ fdroidArchiveRepo.copy(weight = 1),
+ fdroidRepo.copy(weight = 2),
+ guardianArchiveRepo.copy(weight = 3),
+ guardianRepo.copy(weight = 4),
+ )
+ runRepoMigration(reposToMigrate) { db ->
+ db
+ .getRepositoryDao()
+ .getRepositories()
+ .sortedByDescending { it.weight }
+ .also { repos ->
+ assertEquals(reposToMigrate.size, repos.size)
+ assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
+ assertEquals(guardianRepo.address, repos[0].address)
+ assertEquals(1_000_000_000, repos[0].weight)
+ assertEquals(guardianArchiveRepo.address, repos[1].address)
+ assertEquals(999_999_999, repos[1].weight)
+ assertEquals(fdroidRepo.address, repos[2].address)
+ assertEquals(999_999_998, repos[2].weight)
+ assertEquals(fdroidArchiveRepo.address, repos[3].address)
+ assertEquals(999_999_997, repos[3].weight)
+ }
+ }
+ }
+
+ @Test
+ fun migrateOldDefaultReposPlusRandomOnes() {
+ val reposToMigrate =
+ listOf(
+ fdroidArchiveRepo.copy(weight = 1),
+ fdroidRepo.copy(weight = 2),
+ guardianArchiveRepo.copy(weight = 3),
+ guardianRepo.copy(weight = 4),
+ InitialRepository(
+ name = "Foo bar",
+ address = "https://example.org/fdroid/repo",
+ description = "foo bar repo",
+ certificate = "1234567890",
+ version = 0L,
+ enabled = true,
+ weight = 5,
+ ),
+ InitialRepository(
+ name = "Bla Blub",
+ address = "https://example.com/fdroid/repo",
+ description = "bla blub repo",
+ certificate = "0987654321",
+ version = 0L,
+ enabled = true,
+ weight = 6,
+ ),
+ )
+ runRepoMigration(reposToMigrate) { db ->
+ db
+ .getRepositoryDao()
+ .getRepositories()
+ .sortedByDescending { it.weight }
+ .also { repos ->
+ assertEquals(reposToMigrate.size, repos.size)
+ assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
+ assertEquals("https://example.com/fdroid/repo", repos[0].address)
+ assertEquals(1_000_000_000, repos[0].weight)
+ assertEquals("https://example.org/fdroid/repo", repos[1].address)
+ assertEquals(999_999_998, repos[1].weight) // space for archive above
+ assertEquals(guardianRepo.address, repos[2].address)
+ assertEquals(999_999_996, repos[2].weight)
+ assertEquals(guardianArchiveRepo.address, repos[3].address)
+ assertEquals(999_999_995, repos[3].weight)
+ assertEquals(fdroidRepo.address, repos[4].address)
+ assertEquals(999_999_994, repos[4].weight)
+ assertEquals(fdroidArchiveRepo.address, repos[5].address)
+ assertEquals(999_999_993, repos[5].weight)
+ }
+ }
+ }
+
+ @Test
+ fun migrateArchiveWithoutMainRepo() {
+ val reposToMigrate =
+ listOf(
+ InitialRepository(
+ name = "Foo bar",
+ address = "https://example.org/fdroid/repo",
+ description = "foo bar repo",
+ certificate = "1234567890",
+ version = 0L,
+ enabled = true,
+ weight = 2,
+ ),
+ fdroidArchiveRepo.copy(weight = 5),
+ guardianRepo.copy(weight = 6),
+ )
+ runRepoMigration(reposToMigrate) { db ->
+ db
+ .getRepositoryDao()
+ .getRepositories()
+ .sortedByDescending { it.weight }
+ .also { repos ->
+ assertEquals(reposToMigrate.size, repos.size)
+ assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
+ assertEquals(guardianRepo.address, repos[0].address)
+ assertEquals(1_000_000_000, repos[0].weight) // space for archive below
+ assertEquals("https://example.org/fdroid/repo", repos[1].address)
+ assertEquals(999_999_998, repos[1].weight)
+ assertEquals(fdroidArchiveRepo.address, repos[2].address) // archive at the end
+ assertEquals(999_999_996, repos[2].weight)
+ }
+ }
+ }
+
+ @Test
+ fun testPreferredRepoChanges() {
+ var repoId: Long
+ val packageName = "org.example"
+ helper.createDatabase(TEST_DB, 1).use { db ->
+ // Database has schema version 1. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ val repo = fdroidRepo
+ repoId =
+ db.insert(
+ CoreRepository.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to repo.name)))
+ put("description", localizedTextV2toString(mapOf("en-US" to repo.description)))
+ put("address", repo.address)
+ put("timestamp", -1)
+ put("certificate", repo.certificate)
+ },
)
- runRepoMigration(reposToMigrate) { db ->
- db.getRepositoryDao().getRepositories().sortedByDescending { it.weight }.also { repos ->
- assertEquals(reposToMigrate.size, repos.size)
- assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
- assertEquals(fdroidRepo.address, repos[0].address)
- assertEquals(1_000_000_000, repos[0].weight)
- assertEquals(fdroidArchiveRepo.address, repos[1].address)
- assertEquals(999_999_999, repos[1].weight)
- }
- }
+ db.insert(
+ RepositoryPreferences.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("enabled", repo.enabled)
+ put("weight", repo.weight)
+ },
+ )
+ // insert an app with empty app prefs
+ db.insert(
+ AppMetadata.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("packageName", packageName)
+ put("added", 23L)
+ put("lastUpdated", 42L)
+ put("isCompatible", true)
+ },
+ )
+ db.insert(
+ AppPrefs.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("packageName", packageName)
+ put("ignoreVersionCodeUpdate", 0)
+ },
+ )
}
- @Test
- fun migrateOldDefaultRepos() {
- val reposToMigrate = listOf(
- fdroidArchiveRepo.copy(weight = 1),
- fdroidRepo.copy(weight = 2),
- guardianArchiveRepo.copy(weight = 3),
- guardianRepo.copy(weight = 4),
+ // Re-open the database with version 2, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
+
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // migrated apps have no preferred repo set
+ assertNotNull(db.getAppDao().getApp(repoId, packageName))
+ val appPrefs = db.getAppPrefsDao().getAppPrefsOrNull(packageName) ?: fail()
+ assertEquals(packageName, appPrefs.packageName)
+ assertNull(appPrefs.preferredRepoId)
+
+ // preferred repo inferred from repo priorities
+ val preferredRepos = db.getAppPrefsDao().getPreferredRepos(listOf(packageName))
+ assertEquals(1, preferredRepos.size)
+ assertEquals(repoId, preferredRepos[packageName])
+ }
+ }
+
+ @Test
+ fun repoWithoutCertificate() {
+ helper.createDatabase(TEST_DB, 1).use { db ->
+ // Database has schema version 1. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ val repoId =
+ db.insert(
+ CoreRepository.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to fdroidRepo.name)))
+ put("description", localizedTextV2toString(mapOf("en-US" to fdroidRepo.description)))
+ put("address", fdroidRepo.address)
+ put("timestamp", -1)
+ },
)
- runRepoMigration(reposToMigrate) { db ->
- db.getRepositoryDao().getRepositories().sortedByDescending { it.weight }.also { repos ->
- assertEquals(reposToMigrate.size, repos.size)
- assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
- assertEquals(guardianRepo.address, repos[0].address)
- assertEquals(1_000_000_000, repos[0].weight)
- assertEquals(guardianArchiveRepo.address, repos[1].address)
- assertEquals(999_999_999, repos[1].weight)
- assertEquals(fdroidRepo.address, repos[2].address)
- assertEquals(999_999_998, repos[2].weight)
- assertEquals(fdroidArchiveRepo.address, repos[3].address)
- assertEquals(999_999_997, repos[3].weight)
- }
- }
+ db.insert(
+ RepositoryPreferences.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("enabled", fdroidRepo.enabled)
+ put("weight", fdroidRepo.weight)
+ },
+ )
}
- @Test
- fun migrateOldDefaultReposPlusRandomOnes() {
- val reposToMigrate = listOf(
- fdroidArchiveRepo.copy(weight = 1),
- fdroidRepo.copy(weight = 2),
- guardianArchiveRepo.copy(weight = 3),
- guardianRepo.copy(weight = 4),
- InitialRepository(
- name = "Foo bar",
- address = "https://example.org/fdroid/repo",
- description = "foo bar repo",
- certificate = "1234567890",
- version = 0L,
- enabled = true,
- weight = 5,
- ),
- InitialRepository(
- name = "Bla Blub",
- address = "https://example.com/fdroid/repo",
- description = "bla blub repo",
- certificate = "0987654321",
- version = 0L,
- enabled = true,
- weight = 6,
- ),
+ // Re-open the database with version 2, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
+
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // repo without cert did not get migrated, because we auto-migrate to latest version
+ assertEquals(0, db.getRepositoryDao().getRepositories().size)
+ }
+ }
+
+ private fun runRepoMigration(repos: List, check: (FDroidDatabaseInt) -> Unit) {
+ helper.createDatabase(TEST_DB, 1).use { db ->
+ // Database has schema version 1. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ repos.forEach { repo ->
+ val repoId =
+ db.insert(
+ CoreRepository.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to repo.name)))
+ put("description", localizedTextV2toString(mapOf("en-US" to repo.description)))
+ put("address", repo.address)
+ put("timestamp", -1)
+ put("certificate", repo.certificate)
+ },
+ )
+ db.insert(
+ RepositoryPreferences.TABLE,
+ CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("enabled", repo.enabled)
+ put("weight", repo.weight)
+ },
)
- runRepoMigration(reposToMigrate) { db ->
- db.getRepositoryDao().getRepositories().sortedByDescending { it.weight }.also { repos ->
- assertEquals(reposToMigrate.size, repos.size)
- assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
- assertEquals("https://example.com/fdroid/repo", repos[0].address)
- assertEquals(1_000_000_000, repos[0].weight)
- assertEquals("https://example.org/fdroid/repo", repos[1].address)
- assertEquals(999_999_998, repos[1].weight) // space for archive above
- assertEquals(guardianRepo.address, repos[2].address)
- assertEquals(999_999_996, repos[2].weight)
- assertEquals(guardianArchiveRepo.address, repos[3].address)
- assertEquals(999_999_995, repos[3].weight)
- assertEquals(fdroidRepo.address, repos[4].address)
- assertEquals(999_999_994, repos[4].weight)
- assertEquals(fdroidArchiveRepo.address, repos[5].address)
- assertEquals(999_999_993, repos[5].weight)
- }
- }
+ }
}
- @Test
- fun migrateArchiveWithoutMainRepo() {
- val reposToMigrate = listOf(
- InitialRepository(
- name = "Foo bar",
- address = "https://example.org/fdroid/repo",
- description = "foo bar repo",
- certificate = "1234567890",
- version = 0L,
- enabled = true,
- weight = 2,
- ),
- fdroidArchiveRepo.copy(weight = 5),
- guardianRepo.copy(weight = 6),
- )
- runRepoMigration(reposToMigrate) { db ->
- db.getRepositoryDao().getRepositories().sortedByDescending { it.weight }.also { repos ->
- assertEquals(reposToMigrate.size, repos.size)
- assertEquals(reposToMigrate.size, repos.map { it.weight }.toSet().size)
- assertEquals(guardianRepo.address, repos[0].address)
- assertEquals(1_000_000_000, repos[0].weight) // space for archive below
- assertEquals("https://example.org/fdroid/repo", repos[1].address)
- assertEquals(999_999_998, repos[1].weight)
- assertEquals(fdroidArchiveRepo.address, repos[2].address) // archive at the end
- assertEquals(999_999_996, repos[2].weight)
- }
- }
- }
+ // Re-open the database with version 2, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
- @Test
- fun testPreferredRepoChanges() {
- var repoId: Long
- val packageName = "org.example"
- helper.createDatabase(TEST_DB, 1).use { db ->
- // Database has schema version 1. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- val repo = fdroidRepo
- repoId = db.insert(CoreRepository.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to repo.name)))
- put("description", localizedTextV2toString(mapOf("en-US" to repo.description)))
- put("address", repo.address)
- put("timestamp", -1)
- put("certificate", repo.certificate)
- })
- db.insert(RepositoryPreferences.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("enabled", repo.enabled)
- put("weight", repo.weight)
- })
- // insert an app with empty app prefs
- db.insert(AppMetadata.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("packageName", packageName)
- put("added", 23L)
- put("lastUpdated", 42L)
- put("isCompatible", true)
- })
- db.insert(AppPrefs.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("packageName", packageName)
- put("ignoreVersionCodeUpdate", 0)
- })
- }
-
- // Re-open the database with version 2, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build()
- .use { db ->
- // migrated apps have no preferred repo set
- assertNotNull(db.getAppDao().getApp(repoId, packageName))
- val appPrefs = db.getAppPrefsDao().getAppPrefsOrNull(packageName) ?: fail()
- assertEquals(packageName, appPrefs.packageName)
- assertNull(appPrefs.preferredRepoId)
-
- // preferred repo inferred from repo priorities
- val preferredRepos = db.getAppPrefsDao().getPreferredRepos(listOf(packageName))
- assertEquals(1, preferredRepos.size)
- assertEquals(repoId, preferredRepos[packageName])
- }
- }
-
- @Test
- fun repoWithoutCertificate() {
- helper.createDatabase(TEST_DB, 1).use { db ->
- // Database has schema version 1. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- val repoId = db.insert(CoreRepository.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to fdroidRepo.name)))
- put(
- "description",
- localizedTextV2toString(mapOf("en-US" to fdroidRepo.description))
- )
- put("address", fdroidRepo.address)
- put("timestamp", -1)
- })
- db.insert(RepositoryPreferences.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("enabled", fdroidRepo.enabled)
- put("weight", fdroidRepo.weight)
- })
- }
-
- // Re-open the database with version 2, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build().use { db ->
- // repo without cert did not get migrated, because we auto-migrate to latest version
- assertEquals(0, db.getRepositoryDao().getRepositories().size)
- }
- }
-
- private fun runRepoMigration(
- repos: List,
- check: (FDroidDatabaseInt) -> Unit,
- ) {
- helper.createDatabase(TEST_DB, 1).use { db ->
- // Database has schema version 1. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- repos.forEach { repo ->
- val repoId = db.insert(CoreRepository.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to repo.name)))
- put("description", localizedTextV2toString(mapOf("en-US" to repo.description)))
- put("address", repo.address)
- put("timestamp", -1)
- put("certificate", repo.certificate)
- })
- db.insert(RepositoryPreferences.TABLE, CONFLICT_FAIL, ContentValues().apply {
- put("repoId", repoId)
- put("enabled", repo.enabled)
- put("weight", repo.weight)
- })
- }
- }
-
- // Re-open the database with version 2, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 2, true).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build().use { db ->
- check(db)
- }
- }
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db -> check(db) }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/PreferredRepoMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/PreferredRepoMigrationTest.kt
index 8a30dbd57..cb7d8134b 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/PreferredRepoMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/PreferredRepoMigrationTest.kt
@@ -8,92 +8,96 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.random.Random
+import kotlin.test.assertEquals
import org.fdroid.database.Converters.localizedTextV2toString
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
private const val TEST_DB = "migration-test"
@RunWith(AndroidJUnit4::class)
internal class PreferredRepoMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- instrumentation = InstrumentationRegistry.getInstrumentation(),
- databaseClass = FDroidDatabaseInt::class.java,
- specs = emptyList(),
- openFactory = FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ instrumentation = InstrumentationRegistry.getInstrumentation(),
+ databaseClass = FDroidDatabaseInt::class.java,
+ specs = emptyList(),
+ openFactory = FrameworkSQLiteOpenHelperFactory(),
)
- @Test
- fun migrateRepos() {
- helper.createDatabase(TEST_DB, 6).use { db ->
- // Database has schema version 6. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- val repoId = db.insert(
- CoreRepository.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to "foo")))
- put("description", localizedTextV2toString(mapOf("en-US" to "bar")))
- put("address", "https://example.org/repo")
- put("certificate", "0123")
- put("timestamp", -1)
- },
- )
- db.insert(
- RepositoryPreferences.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("repoId", repoId)
- put("enabled", true)
- put("weight", Long.MAX_VALUE)
- },
- )
- val oeffiMetadata = ContentValues().apply {
- put("packageName", "de.schildbach.oeffi")
- put("repoId", repoId)
- put("localizedName", "Öffi")
- put("localizedSummary", "Der König des Fahrplandschungels!")
- put("added", Random.nextLong())
- put("lastUpdated", Random.nextLong())
- put("isCompatible", true)
- }
- db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiMetadata)
- val oeffiPrefs = ContentValues().apply {
- put("packageName", "de.schildbach.oeffi")
- put("ignoreVersionCodeUpdate", 0)
- put("preferredRepoId", 42) // repo doesn't exist
- }
- db.insert(AppPrefs.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiPrefs)
- }
-
- // Re-open the database with version 2, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 7, true).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- Room.databaseBuilder(
- ApplicationProvider.getApplicationContext(),
- FDroidDatabaseInt::class.java,
- TEST_DB
+ @Test
+ fun migrateRepos() {
+ helper.createDatabase(TEST_DB, 6).use { db ->
+ // Database has schema version 6. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ val repoId =
+ db.insert(
+ CoreRepository.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to "foo")))
+ put("description", localizedTextV2toString(mapOf("en-US" to "bar")))
+ put("address", "https://example.org/repo")
+ put("certificate", "0123")
+ put("timestamp", -1)
+ },
)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build().use { db ->
- // repo without cert did not get migrated, the other one did
- assertEquals(1, db.getRepositoryDao().getRepositories().size)
- val repo = db.getRepositoryDao().getRepositories()[0]
- assertEquals("https://example.org/repo", repo.address)
-
- db.getAppPrefsDao().getPreferredRepos(listOf("de.schildbach.oeffi")).let {
- assertEquals(1, it.size)
- // now correct PreferredRepo gets returned
- assertEquals(repo.repoId, it["de.schildbach.oeffi"])
- }
- }
+ db.insert(
+ RepositoryPreferences.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId)
+ put("enabled", true)
+ put("weight", Long.MAX_VALUE)
+ },
+ )
+ val oeffiMetadata =
+ ContentValues().apply {
+ put("packageName", "de.schildbach.oeffi")
+ put("repoId", repoId)
+ put("localizedName", "Öffi")
+ put("localizedSummary", "Der König des Fahrplandschungels!")
+ put("added", Random.nextLong())
+ put("lastUpdated", Random.nextLong())
+ put("isCompatible", true)
+ }
+ db.insert(AppMetadata.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiMetadata)
+ val oeffiPrefs =
+ ContentValues().apply {
+ put("packageName", "de.schildbach.oeffi")
+ put("ignoreVersionCodeUpdate", 0)
+ put("preferredRepoId", 42) // repo doesn't exist
+ }
+ db.insert(AppPrefs.TABLE, SQLiteDatabase.CONFLICT_FAIL, oeffiPrefs)
}
+ // Re-open the database with version 2, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 7, true).close()
+
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ Room.databaseBuilder(
+ ApplicationProvider.getApplicationContext(),
+ FDroidDatabaseInt::class.java,
+ TEST_DB,
+ )
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // repo without cert did not get migrated, the other one did
+ assertEquals(1, db.getRepositoryDao().getRepositories().size)
+ val repo = db.getRepositoryDao().getRepositories()[0]
+ assertEquals("https://example.org/repo", repo.address)
+
+ db.getAppPrefsDao().getPreferredRepos(listOf("de.schildbach.oeffi")).let {
+ assertEquals(1, it.size)
+ // now correct PreferredRepo gets returned
+ assertEquals(repo.repoId, it["de.schildbach.oeffi"])
+ }
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt
index 167210a88..c1ecc3aee 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/RepoCertNonNullMigrationTest.kt
@@ -8,84 +8,87 @@ import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import kotlin.test.assertEquals
import org.fdroid.database.Converters.localizedTextV2toString
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
private const val TEST_DB = "migration-test"
@RunWith(AndroidJUnit4::class)
internal class RepoCertNonNullMigrationTest {
- @get:Rule
- val helper: MigrationTestHelper = MigrationTestHelper(
- instrumentation = getInstrumentation(),
- databaseClass = FDroidDatabaseInt::class.java,
- specs = emptyList(),
- openFactory = FrameworkSQLiteOpenHelperFactory(),
+ @get:Rule
+ val helper: MigrationTestHelper =
+ MigrationTestHelper(
+ instrumentation = getInstrumentation(),
+ databaseClass = FDroidDatabaseInt::class.java,
+ specs = emptyList(),
+ openFactory = FrameworkSQLiteOpenHelperFactory(),
)
- @Test
- fun migrateRepos() {
- helper.createDatabase(TEST_DB, 2).use { db ->
- // Database has schema version 2. Insert some data using SQL queries.
- // We can't use DAO classes because they expect the latest schema.
- val repoId1 = db.insert(
- CoreRepository.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to "foo")))
- put("description", localizedTextV2toString(mapOf("en-US" to "bar")))
- put("address", "https://example.org/repo")
- put("certificate", "0123")
- put("timestamp", -1)
- },
- )
- db.insert(
- RepositoryPreferences.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("repoId", repoId1)
- put("enabled", true)
- put("weight", Long.MAX_VALUE)
- },
- )
- val repoId2 = db.insert(
- CoreRepository.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("name", localizedTextV2toString(mapOf("en-US" to "no cert")))
- put("description", localizedTextV2toString(mapOf("en-US" to "no cert desc")))
- put("address", "https://example.com/repo")
- put("timestamp", -1)
- },
- )
- db.insert(
- RepositoryPreferences.TABLE,
- SQLiteDatabase.CONFLICT_FAIL,
- ContentValues().apply {
- put("repoId", repoId2)
- put("enabled", true)
- put("weight", Long.MAX_VALUE - 2)
- },
- )
- }
-
- // Re-open the database with version 2, auto-migrations are applied automatically
- helper.runMigrationsAndValidate(TEST_DB, 4, true, MIGRATION_2_3).close()
-
- // now get the Room DB, so we can use our DAOs for verifying the migration
- Room.databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
- .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
- .allowMainThreadQueries()
- .build().use { db ->
- // repo without cert did not get migrated, the other one did
- assertEquals(1, db.getRepositoryDao().getRepositories().size)
- val repo = db.getRepositoryDao().getRepositories()[0]
- assertEquals("https://example.org/repo", repo.address)
- }
+ @Test
+ fun migrateRepos() {
+ helper.createDatabase(TEST_DB, 2).use { db ->
+ // Database has schema version 2. Insert some data using SQL queries.
+ // We can't use DAO classes because they expect the latest schema.
+ val repoId1 =
+ db.insert(
+ CoreRepository.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to "foo")))
+ put("description", localizedTextV2toString(mapOf("en-US" to "bar")))
+ put("address", "https://example.org/repo")
+ put("certificate", "0123")
+ put("timestamp", -1)
+ },
+ )
+ db.insert(
+ RepositoryPreferences.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId1)
+ put("enabled", true)
+ put("weight", Long.MAX_VALUE)
+ },
+ )
+ val repoId2 =
+ db.insert(
+ CoreRepository.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("name", localizedTextV2toString(mapOf("en-US" to "no cert")))
+ put("description", localizedTextV2toString(mapOf("en-US" to "no cert desc")))
+ put("address", "https://example.com/repo")
+ put("timestamp", -1)
+ },
+ )
+ db.insert(
+ RepositoryPreferences.TABLE,
+ SQLiteDatabase.CONFLICT_FAIL,
+ ContentValues().apply {
+ put("repoId", repoId2)
+ put("enabled", true)
+ put("weight", Long.MAX_VALUE - 2)
+ },
+ )
}
+ // Re-open the database with version 2, auto-migrations are applied automatically
+ helper.runMigrationsAndValidate(TEST_DB, 4, true, MIGRATION_2_3).close()
+
+ // now get the Room DB, so we can use our DAOs for verifying the migration
+ Room.databaseBuilder(getApplicationContext(), FDroidDatabaseInt::class.java, TEST_DB)
+ .addMigrations(MIGRATION_2_3, MIGRATION_5_6, MIGRATION_8_9)
+ .allowMainThreadQueries()
+ .build()
+ .use { db ->
+ // repo without cert did not get migrated, the other one did
+ assertEquals(1, db.getRepositoryDao().getRepositories().size)
+ val repo = db.getRepositoryDao().getRepositories()[0]
+ assertEquals("https://example.org/repo", repo.address)
+ }
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt
index 1a1f079c5..71bb2c3cf 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDaoTest.kt
@@ -1,6 +1,13 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.fdroid.database.TestUtils.assertRepoEquals
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
@@ -10,372 +17,365 @@ import org.fdroid.test.TestUtils.orNull
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
-import kotlin.test.assertFalse
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class RepositoryDaoTest : DbTest() {
- @Test
- fun testInsertInitialRepository() {
- val repo = InitialRepository(
- name = getRandomString(),
- address = getRandomString(),
- mirrors = listOf(getRandomString(), getRandomString(), getRandomString()),
- description = getRandomString(),
- certificate = "abcdef", // not random, because format gets checked
- version = Random.nextLong(),
- enabled = Random.nextBoolean(),
- )
- val repoId = repoDao.insert(repo)
+ @Test
+ fun testInsertInitialRepository() {
+ val repo =
+ InitialRepository(
+ name = getRandomString(),
+ address = getRandomString(),
+ mirrors = listOf(getRandomString(), getRandomString(), getRandomString()),
+ description = getRandomString(),
+ certificate = "abcdef", // not random, because format gets checked
+ version = Random.nextLong(),
+ enabled = Random.nextBoolean(),
+ )
+ val repoId = repoDao.insert(repo)
- val actualRepo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(repo.name, actualRepo.getName(locales))
- assertEquals(repo.address, actualRepo.address)
- assertEquals(repo.description, actualRepo.getDescription(locales))
- assertEquals(repo.certificate, actualRepo.certificate)
- assertEquals(repo.version, actualRepo.version)
- assertEquals(repo.enabled, actualRepo.enabled)
- assertEquals(Int.MAX_VALUE - 2, actualRepo.weight) // ignoring provided weight
- assertEquals(-1, actualRepo.timestamp)
- assertEquals(3, actualRepo.mirrors.size)
- assertEquals(emptyList(), actualRepo.userMirrors)
- assertEquals(emptyList(), actualRepo.disabledMirrors)
- assertEquals(repo.mirrors.toSet(), actualRepo.mirrors.map { it.url }.toSet())
- assertEquals(emptyList(), actualRepo.antiFeatures)
- assertEquals(emptyList(), actualRepo.categories)
- assertEquals(emptyList(), actualRepo.releaseChannels)
- assertNull(actualRepo.formatVersion)
- assertNull(actualRepo.repository.icon)
- assertNull(actualRepo.lastUpdated)
- assertNull(actualRepo.webBaseUrl)
+ val actualRepo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(repo.name, actualRepo.getName(locales))
+ assertEquals(repo.address, actualRepo.address)
+ assertEquals(repo.description, actualRepo.getDescription(locales))
+ assertEquals(repo.certificate, actualRepo.certificate)
+ assertEquals(repo.version, actualRepo.version)
+ assertEquals(repo.enabled, actualRepo.enabled)
+ assertEquals(Int.MAX_VALUE - 2, actualRepo.weight) // ignoring provided weight
+ assertEquals(-1, actualRepo.timestamp)
+ assertEquals(3, actualRepo.mirrors.size)
+ assertEquals(emptyList(), actualRepo.userMirrors)
+ assertEquals(emptyList(), actualRepo.disabledMirrors)
+ assertEquals(repo.mirrors.toSet(), actualRepo.mirrors.map { it.url }.toSet())
+ assertEquals(emptyList(), actualRepo.antiFeatures)
+ assertEquals(emptyList(), actualRepo.categories)
+ assertEquals(emptyList(), actualRepo.releaseChannels)
+ assertNull(actualRepo.formatVersion)
+ assertNull(actualRepo.repository.icon)
+ assertNull(actualRepo.lastUpdated)
+ assertNull(actualRepo.webBaseUrl)
+ }
+
+ @Test
+ fun testInsertEmptyRepo() {
+ // insert empty repo
+ val address = getRandomString()
+ val username = getRandomString().orNull()
+ val password = getRandomString().orNull()
+ val repoId = repoDao.insertEmptyRepo(address, username, password)
+
+ // check that repo got inserted as expected
+ val actualRepo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(address, actualRepo.address)
+ assertEquals(username, actualRepo.username)
+ assertEquals(password, actualRepo.password)
+ assertEquals(-1, actualRepo.timestamp)
+ assertEquals(listOf(org.fdroid.download.Mirror(address)), actualRepo.getMirrors())
+ assertEquals(emptyList(), actualRepo.antiFeatures)
+ assertEquals(emptyList(), actualRepo.categories)
+ assertEquals(emptyList(), actualRepo.releaseChannels)
+ assertNull(actualRepo.formatVersion)
+ assertNull(actualRepo.repository.icon)
+ assertNull(actualRepo.lastUpdated)
+ assertNull(actualRepo.webBaseUrl)
+ }
+
+ @Test
+ fun insertAndDeleteTwoRepos() {
+ // insert first repo
+ val repo1 = getRandomRepo()
+ val repoId1 = repoDao.insertOrReplace(repo1)
+
+ // check that first repo got added and retrieved as expected
+ repoDao.getRepositories().let { repos ->
+ assertEquals(1, repos.size)
+ assertRepoEquals(repo1, repos[0])
}
+ val repositoryPreferences1 = repoDao.getRepositoryPreferences(repoId1)
+ assertEquals(repoId1, repositoryPreferences1?.repoId)
- @Test
- fun testInsertEmptyRepo() {
- // insert empty repo
- val address = getRandomString()
- val username = getRandomString().orNull()
- val password = getRandomString().orNull()
- val repoId = repoDao.insertEmptyRepo(address, username, password)
+ // insert second repo
+ val repo2 = getRandomRepo()
+ val repoId2 = repoDao.insertOrReplace(repo2)
- // check that repo got inserted as expected
- val actualRepo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(address, actualRepo.address)
- assertEquals(username, actualRepo.username)
- assertEquals(password, actualRepo.password)
- assertEquals(-1, actualRepo.timestamp)
- assertEquals(listOf(org.fdroid.download.Mirror(address)), actualRepo.getMirrors())
- assertEquals(emptyList(), actualRepo.antiFeatures)
- assertEquals(emptyList(), actualRepo.categories)
- assertEquals(emptyList(), actualRepo.releaseChannels)
- assertNull(actualRepo.formatVersion)
- assertNull(actualRepo.repository.icon)
- assertNull(actualRepo.lastUpdated)
- assertNull(actualRepo.webBaseUrl)
+ // check that both repos got added and retrieved as expected
+ listOf(
+ repoDao.getRepositories().sortedBy { it.repoId },
+ repoDao.getLiveRepositories().getOrFail().sortedBy { it.repoId },
+ )
+ .forEach { repos ->
+ assertEquals(2, repos.size)
+ assertRepoEquals(repo1, repos[0])
+ assertRepoEquals(repo2, repos[1])
+ }
+ val repositoryPreferences2 = repoDao.getRepositoryPreferences(repoId2)
+ assertEquals(repoId2, repositoryPreferences2?.repoId)
+ // second repo has one weight point more than first repo
+ assertEquals(repositoryPreferences1?.weight?.minus(2), repositoryPreferences2?.weight)
+
+ // remove first repo and check that the database only returns one
+ repoDao.deleteRepository(repoId1)
+ listOf(repoDao.getRepositories(), repoDao.getLiveRepositories().getOrFail()).forEach { repos ->
+ assertEquals(1, repos.size)
+ assertRepoEquals(repo2, repos[0])
}
+ assertNull(repoDao.getRepositoryPreferences(repoId1))
- @Test
- fun insertAndDeleteTwoRepos() {
- // insert first repo
- val repo1 = getRandomRepo()
- val repoId1 = repoDao.insertOrReplace(repo1)
+ // remove second repo and check that all associated data got removed as well
+ repoDao.deleteRepository(repoId2)
+ assertEquals(0, repoDao.getRepositories().size)
+ assertEquals(0, repoDao.countMirrors())
+ assertEquals(0, repoDao.countAntiFeatures())
+ assertEquals(0, repoDao.countCategories())
+ assertEquals(0, repoDao.countReleaseChannels())
+ assertNull(repoDao.getRepositoryPreferences(repoId2))
+ }
- // check that first repo got added and retrieved as expected
- repoDao.getRepositories().let { repos ->
- assertEquals(1, repos.size)
- assertRepoEquals(repo1, repos[0])
- }
- val repositoryPreferences1 = repoDao.getRepositoryPreferences(repoId1)
- assertEquals(repoId1, repositoryPreferences1?.repoId)
+ @Test
+ fun insertTwoReposAndClearAll() {
+ val repo1 = getRandomRepo()
+ val repo2 = getRandomRepo()
+ repoDao.insertOrReplace(repo1)
+ repoDao.insertOrReplace(repo2)
+ assertEquals(2, repoDao.getRepositories().size)
+ assertEquals(2, repoDao.getLiveRepositories().getOrFail().size)
- // insert second repo
- val repo2 = getRandomRepo()
- val repoId2 = repoDao.insertOrReplace(repo2)
+ repoDao.clearAll()
+ assertEquals(0, repoDao.getRepositories().size)
+ assertEquals(0, repoDao.getLiveRepositories().getOrFail().size)
+ }
- // check that both repos got added and retrieved as expected
- listOf(
- repoDao.getRepositories().sortedBy { it.repoId },
- repoDao.getLiveRepositories().getOrFail().sortedBy { it.repoId },
- ).forEach { repos ->
- assertEquals(2, repos.size)
- assertRepoEquals(repo1, repos[0])
- assertRepoEquals(repo2, repos[1])
- }
- val repositoryPreferences2 = repoDao.getRepositoryPreferences(repoId2)
- assertEquals(repoId2, repositoryPreferences2?.repoId)
- // second repo has one weight point more than first repo
- assertEquals(repositoryPreferences1?.weight?.minus(2), repositoryPreferences2?.weight)
+ @Test
+ fun testGetRepositoryByCert() {
+ val cert = getRandomString()
+ // insert repo and (required) preferences
+ val repo1 = getRandomRepo().toCoreRepository(version = 42L, certificate = cert)
+ val repoId = repoDao.insertOrReplace(repo1)
+ val repositoryPreferences = RepositoryPreferences(repoId, 3)
+ repoDao.insert(repositoryPreferences)
- // remove first repo and check that the database only returns one
- repoDao.deleteRepository(repoId1)
- listOf(
- repoDao.getRepositories(),
- repoDao.getLiveRepositories().getOrFail(),
- ).forEach { repos ->
- assertEquals(1, repos.size)
- assertRepoEquals(repo2, repos[0])
- }
- assertNull(repoDao.getRepositoryPreferences(repoId1))
+ // repo is returned when querying for right cert
+ assertEquals(repo1.copy(repoId = repoId), repoDao.getRepository(cert)?.repository)
+ // nothing is returned when querying for non-existent cert
+ assertNull(repoDao.getRepository("foo bar"))
+ }
- // remove second repo and check that all associated data got removed as well
- repoDao.deleteRepository(repoId2)
- assertEquals(0, repoDao.getRepositories().size)
- assertEquals(0, repoDao.countMirrors())
- assertEquals(0, repoDao.countAntiFeatures())
- assertEquals(0, repoDao.countCategories())
- assertEquals(0, repoDao.countReleaseChannels())
- assertNull(repoDao.getRepositoryPreferences(repoId2))
+ @Test
+ fun testSetRepositoryEnabled() {
+ // repo is enabled by default
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail())
+
+ // disabled repo is disabled
+ repoDao.setRepositoryEnabled(repoId, false)
+ assertFalse(repoDao.getRepository(repoId)?.enabled ?: fail())
+
+ // enabling again works
+ repoDao.setRepositoryEnabled(repoId, true)
+ assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail())
+ }
+
+ @Test
+ fun testUpdateUserMirrors() {
+ // repo is enabled by default
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors)
+
+ // add user mirrors
+ val userMirrors = listOf(getRandomString(), getRandomString(), getRandomString())
+ repoDao.updateUserMirrors(repoId, userMirrors)
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(userMirrors, repo.userMirrors)
+
+ // user mirrors are part of all mirrors
+ val userDownloadMirrors = userMirrors.map { org.fdroid.download.Mirror(it) }
+ assertTrue(repo.getMirrors().containsAll(userDownloadMirrors))
+
+ // remove user mirrors
+ repoDao.updateUserMirrors(repoId, emptyList())
+ assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors)
+ }
+
+ @Test
+ fun testUpdateUsernameAndPassword() {
+ // repo has no username or password initially
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ repoDao.getRepository(repoId)?.let { repo ->
+ assertEquals(null, repo.username)
+ assertEquals(null, repo.password)
+ } ?: fail()
+
+ // add user name and password
+ val username = getRandomString().orNull()
+ val password = getRandomString().orNull()
+ repoDao.updateUsernameAndPassword(repoId, username, password)
+ repoDao.getRepository(repoId)?.let { repo ->
+ assertEquals(username, repo.username)
+ assertEquals(password, repo.password)
+ } ?: fail()
+ }
+
+ @Test
+ fun testUpdateDisabledMirrors() {
+ // repo has no username or password initially
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ repoDao.getRepository(repoId)?.let { repo ->
+ assertEquals(null, repo.username)
+ assertEquals(null, repo.password)
+ } ?: fail()
+
+ // add user name and password
+ val username = getRandomString().orNull()
+ val password = getRandomString().orNull()
+ repoDao.updateUsernameAndPassword(repoId, username, password)
+ repoDao.getRepository(repoId)?.let { repo ->
+ assertEquals(username, repo.username)
+ assertEquals(password, repo.password)
+ } ?: fail()
+ }
+
+ @Test
+ fun clearingRepoRemovesAllAssociatedData() {
+ // insert one repo with one app with one version
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ val repositoryPreferences = repoDao.getRepositoryPreferences(repoId)
+ val packageName = getRandomString()
+ val versionId = getRandomString()
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ val packageVersion = getRandomPackageVersionV2()
+ versionDao.insert(repoId, packageName, versionId, packageVersion, Random.nextBoolean())
+
+ // data is there as expected
+ assertEquals(1, repoDao.getRepositories().size)
+ assertEquals(1, appDao.getAppMetadata().size)
+ assertEquals(1, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
+ assertTrue(versionDao.getVersionedStrings(repoId, packageName).isNotEmpty())
+
+ // clearing the repo removes apps and versions
+ repoDao.clear(repoId)
+ assertEquals(1, repoDao.getRepositories().size)
+ assertEquals(0, appDao.countApps())
+ assertEquals(0, appDao.countLocalizedFiles())
+ assertEquals(0, appDao.countLocalizedFileLists())
+ assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
+ assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
+ // preferences are not touched by clearing
+ assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId))
+ }
+
+ @Test
+ fun testGetMinRepositoryWeight() {
+ assertEquals(Int.MAX_VALUE, repoDao.getMinRepositoryWeight())
+
+ repoDao.insertOrReplace(getRandomRepo())
+ assertEquals(Int.MAX_VALUE - 2, repoDao.getMinRepositoryWeight())
+
+ repoDao.insertOrReplace(getRandomRepo())
+ assertEquals(Int.MAX_VALUE - 4, repoDao.getMinRepositoryWeight())
+ }
+
+ @Test
+ fun testReorderRepositories() {
+ val repoId1 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId3 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId4 = repoDao.insertOrReplace(getRandomRepo())
+ val repoId5 = repoDao.insertOrReplace(getRandomRepo())
+
+ // repos are listed in the order they entered the DB [1, 2, 3, 4, 5]
+ assertEquals(
+ listOf(repoId1, repoId2, repoId3, repoId4, repoId5),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // 2 gets moved to 5 [1, 3, 4, 5, 2]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId2) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId5) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId1, repoId3, repoId4, repoId5, repoId2),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // 5 gets moved to 1 [5, 1, 3, 4, 2]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId5) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId1) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId5, repoId1, repoId3, repoId4, repoId2),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // 3 gets moved to 5 [3, 5, 1, 4, 2]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId5) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId3, repoId5, repoId1, repoId4, repoId2),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // 3 gets moved to itself, list shouldn't change [3, 5, 1, 4, 2]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId3) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId3, repoId5, repoId1, repoId4, repoId2),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // we'll add an archive repo for repo1 to the list [3, 5, (1, 1a), 4, 2]
+ val repo1 = repoDao.getRepository(repoId1) ?: fail()
+ repoDao.updateRepository(repo1.repository.copy(certificate = "1234abcd"))
+ val repo1a =
+ InitialRepository(
+ name = getRandomString(),
+ address = "https://example.org/archive",
+ description = getRandomString(),
+ certificate = "1234abcd", // same as repo1
+ version = 42L,
+ enabled = false,
+ )
+ val repoId1a = repoDao.insert(repo1a)
+ repoDao.setWeight(repoId1a, repo1.weight - 1)
+
+ // now we move repo 1 to position of repo 2 [3, 5, 4, 2, (1, 1a)]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId1) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId2) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId3, repoId5, repoId4, repoId2, repoId1, repoId1a),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // now move repo 1 and its archive to top position [(1, 1a), 3, 5, 4, 2]
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId1) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId3) ?: fail(),
+ )
+ assertEquals(
+ listOf(repoId1, repoId1a, repoId3, repoId5, repoId4, repoId2),
+ repoDao.getRepositories().map { it.repoId },
+ )
+
+ // archive repos can't be reordered directly
+ assertFailsWith {
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId1a) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId3) ?: fail(),
+ )
}
-
- @Test
- fun insertTwoReposAndClearAll() {
- val repo1 = getRandomRepo()
- val repo2 = getRandomRepo()
- repoDao.insertOrReplace(repo1)
- repoDao.insertOrReplace(repo2)
- assertEquals(2, repoDao.getRepositories().size)
- assertEquals(2, repoDao.getLiveRepositories().getOrFail().size)
-
- repoDao.clearAll()
- assertEquals(0, repoDao.getRepositories().size)
- assertEquals(0, repoDao.getLiveRepositories().getOrFail().size)
- }
-
- @Test
- fun testGetRepositoryByCert() {
- val cert = getRandomString()
- // insert repo and (required) preferences
- val repo1 = getRandomRepo().toCoreRepository(version = 42L, certificate = cert)
- val repoId = repoDao.insertOrReplace(repo1)
- val repositoryPreferences = RepositoryPreferences(repoId, 3)
- repoDao.insert(repositoryPreferences)
-
- // repo is returned when querying for right cert
- assertEquals(repo1.copy(repoId = repoId), repoDao.getRepository(cert)?.repository)
- // nothing is returned when querying for non-existent cert
- assertNull(repoDao.getRepository("foo bar"))
- }
-
- @Test
- fun testSetRepositoryEnabled() {
- // repo is enabled by default
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail())
-
- // disabled repo is disabled
- repoDao.setRepositoryEnabled(repoId, false)
- assertFalse(repoDao.getRepository(repoId)?.enabled ?: fail())
-
- // enabling again works
- repoDao.setRepositoryEnabled(repoId, true)
- assertTrue(repoDao.getRepository(repoId)?.enabled ?: fail())
- }
-
- @Test
- fun testUpdateUserMirrors() {
- // repo is enabled by default
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors)
-
- // add user mirrors
- val userMirrors = listOf(getRandomString(), getRandomString(), getRandomString())
- repoDao.updateUserMirrors(repoId, userMirrors)
- val repo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(userMirrors, repo.userMirrors)
-
- // user mirrors are part of all mirrors
- val userDownloadMirrors = userMirrors.map { org.fdroid.download.Mirror(it) }
- assertTrue(repo.getMirrors().containsAll(userDownloadMirrors))
-
- // remove user mirrors
- repoDao.updateUserMirrors(repoId, emptyList())
- assertEquals(emptyList(), repoDao.getRepository(repoId)?.userMirrors)
- }
-
- @Test
- fun testUpdateUsernameAndPassword() {
- // repo has no username or password initially
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- repoDao.getRepository(repoId)?.let { repo ->
- assertEquals(null, repo.username)
- assertEquals(null, repo.password)
- } ?: fail()
-
- // add user name and password
- val username = getRandomString().orNull()
- val password = getRandomString().orNull()
- repoDao.updateUsernameAndPassword(repoId, username, password)
- repoDao.getRepository(repoId)?.let { repo ->
- assertEquals(username, repo.username)
- assertEquals(password, repo.password)
- } ?: fail()
- }
-
- @Test
- fun testUpdateDisabledMirrors() {
- // repo has no username or password initially
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- repoDao.getRepository(repoId)?.let { repo ->
- assertEquals(null, repo.username)
- assertEquals(null, repo.password)
- } ?: fail()
-
- // add user name and password
- val username = getRandomString().orNull()
- val password = getRandomString().orNull()
- repoDao.updateUsernameAndPassword(repoId, username, password)
- repoDao.getRepository(repoId)?.let { repo ->
- assertEquals(username, repo.username)
- assertEquals(password, repo.password)
- } ?: fail()
- }
-
- @Test
- fun clearingRepoRemovesAllAssociatedData() {
- // insert one repo with one app with one version
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- val repositoryPreferences = repoDao.getRepositoryPreferences(repoId)
- val packageName = getRandomString()
- val versionId = getRandomString()
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- val packageVersion = getRandomPackageVersionV2()
- versionDao.insert(repoId, packageName, versionId, packageVersion, Random.nextBoolean())
-
- // data is there as expected
- assertEquals(1, repoDao.getRepositories().size)
- assertEquals(1, appDao.getAppMetadata().size)
- assertEquals(1, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
- assertTrue(versionDao.getVersionedStrings(repoId, packageName).isNotEmpty())
-
- // clearing the repo removes apps and versions
- repoDao.clear(repoId)
- assertEquals(1, repoDao.getRepositories().size)
- assertEquals(0, appDao.countApps())
- assertEquals(0, appDao.countLocalizedFiles())
- assertEquals(0, appDao.countLocalizedFileLists())
- assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
- assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
- // preferences are not touched by clearing
- assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId))
- }
-
- @Test
- fun testGetMinRepositoryWeight() {
- assertEquals(Int.MAX_VALUE, repoDao.getMinRepositoryWeight())
-
- repoDao.insertOrReplace(getRandomRepo())
- assertEquals(Int.MAX_VALUE - 2, repoDao.getMinRepositoryWeight())
-
- repoDao.insertOrReplace(getRandomRepo())
- assertEquals(Int.MAX_VALUE - 4, repoDao.getMinRepositoryWeight())
- }
-
- @Test
- fun testReorderRepositories() {
- val repoId1 = repoDao.insertOrReplace(getRandomRepo())
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- val repoId3 = repoDao.insertOrReplace(getRandomRepo())
- val repoId4 = repoDao.insertOrReplace(getRandomRepo())
- val repoId5 = repoDao.insertOrReplace(getRandomRepo())
-
- // repos are listed in the order they entered the DB [1, 2, 3, 4, 5]
- assertEquals(
- listOf(repoId1, repoId2, repoId3, repoId4, repoId5),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // 2 gets moved to 5 [1, 3, 4, 5, 2]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId2) ?: fail(),
- repoTarget = repoDao.getRepository(repoId5) ?: fail(),
- )
- assertEquals(
- listOf(repoId1, repoId3, repoId4, repoId5, repoId2),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // 5 gets moved to 1 [5, 1, 3, 4, 2]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId5) ?: fail(),
- repoTarget = repoDao.getRepository(repoId1) ?: fail(),
- )
- assertEquals(
- listOf(repoId5, repoId1, repoId3, repoId4, repoId2),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // 3 gets moved to 5 [3, 5, 1, 4, 2]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
- repoTarget = repoDao.getRepository(repoId5) ?: fail(),
- )
- assertEquals(
- listOf(repoId3, repoId5, repoId1, repoId4, repoId2),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // 3 gets moved to itself, list shouldn't change [3, 5, 1, 4, 2]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
- repoTarget = repoDao.getRepository(repoId3) ?: fail(),
- )
- assertEquals(
- listOf(repoId3, repoId5, repoId1, repoId4, repoId2),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // we'll add an archive repo for repo1 to the list [3, 5, (1, 1a), 4, 2]
- val repo1 = repoDao.getRepository(repoId1) ?: fail()
- repoDao.updateRepository(repo1.repository.copy(certificate = "1234abcd"))
- val repo1a = InitialRepository(
- name = getRandomString(),
- address = "https://example.org/archive",
- description = getRandomString(),
- certificate = "1234abcd", // same as repo1
- version = 42L,
- enabled = false,
- )
- val repoId1a = repoDao.insert(repo1a)
- repoDao.setWeight(repoId1a, repo1.weight - 1)
-
- // now we move repo 1 to position of repo 2 [3, 5, 4, 2, (1, 1a)]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId1) ?: fail(),
- repoTarget = repoDao.getRepository(repoId2) ?: fail(),
- )
- assertEquals(
- listOf(repoId3, repoId5, repoId4, repoId2, repoId1, repoId1a),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // now move repo 1 and its archive to top position [(1, 1a), 3, 5, 4, 2]
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId1) ?: fail(),
- repoTarget = repoDao.getRepository(repoId3) ?: fail(),
- )
- assertEquals(
- listOf(repoId1, repoId1a, repoId3, repoId5, repoId4, repoId2),
- repoDao.getRepositories().map { it.repoId },
- )
-
- // archive repos can't be reordered directly
- assertFailsWith {
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId1a) ?: fail(),
- repoTarget = repoDao.getRepository(repoId3) ?: fail(),
- )
- }
- assertFailsWith {
- repoDao.reorderRepositories(
- repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
- repoTarget = repoDao.getRepository(repoId1a) ?: fail(),
- )
- }
+ assertFailsWith {
+ repoDao.reorderRepositories(
+ repoToReorder = repoDao.getRepository(repoId3) ?: fail(),
+ repoTarget = repoDao.getRepository(repoId1a) ?: fail(),
+ )
}
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDiffTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDiffTest.kt
index 432d70a8e..d7d501f2a 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDiffTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/RepositoryDiffTest.kt
@@ -1,6 +1,9 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import org.fdroid.database.TestUtils.assertRepoEquals
@@ -20,191 +23,210 @@ import org.fdroid.test.TestUtils.getRandomMap
import org.fdroid.test.TestUtils.getRandomString
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
-import kotlin.test.assertTrue
-/**
- * Tests that repository diffs get applied to the database correctly.
- */
+/** Tests that repository diffs get applied to the database correctly. */
@RunWith(AndroidJUnit4::class)
internal class RepositoryDiffTest : DbTest() {
- private val j = Json
+ private val j = Json
- @Test
- fun timestampDiff() {
- val repo = getRandomRepo()
- val updateTimestamp = repo.timestamp + 1
- val json = """
+ @Test
+ fun timestampDiff() {
+ val repo = getRandomRepo()
+ val updateTimestamp = repo.timestamp + 1
+ val json =
+ """
{
"timestamp": $updateTimestamp
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- assertEquals(updateTimestamp, repos[0].timestamp)
- assertRepoEquals(repo.copy(timestamp = updateTimestamp), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ assertEquals(updateTimestamp, repos[0].timestamp)
+ assertRepoEquals(repo.copy(timestamp = updateTimestamp), repos[0])
}
+ }
- @Test
- fun timestampDiffTwoReposInDb() {
- // insert repo
- val repo = getRandomRepo()
- repoDao.insertOrReplace(repo)
+ @Test
+ fun timestampDiffTwoReposInDb() {
+ // insert repo
+ val repo = getRandomRepo()
+ repoDao.insertOrReplace(repo)
- // insert another repo before updating
- repoDao.insertOrReplace(getRandomRepo())
+ // insert another repo before updating
+ repoDao.insertOrReplace(getRandomRepo())
- // check that the repo got added and retrieved as expected
- var repos = repoDao.getRepositories().sortedBy { it.repoId }
- assertEquals(2, repos.size)
- val repoId = repos[0].repoId
+ // check that the repo got added and retrieved as expected
+ var repos = repoDao.getRepositories().sortedBy { it.repoId }
+ assertEquals(2, repos.size)
+ val repoId = repos[0].repoId
- val updateTimestamp = Random.nextLong()
- val json = """
+ val updateTimestamp = Random.nextLong()
+ val json =
+ """
{
"timestamp": $updateTimestamp,
"unknown": "field"
- }""".trimIndent()
+ }"""
+ .trimIndent()
- // decode diff from JSON and update DB with it
- val diff = j.parseToJsonElement(json).jsonObject // Json.decodeFromString(json)
- repoDao.updateRepository(repoId, 42, diff)
+ // decode diff from JSON and update DB with it
+ val diff = j.parseToJsonElement(json).jsonObject // Json.decodeFromString(json)
+ repoDao.updateRepository(repoId, 42, diff)
- // fetch repos again and check that the result is as expected
- repos = repoDao.getRepositories().sortedBy { it.repoId }
- assertEquals(2, repos.size)
- assertEquals(repoId, repos[0].repoId)
- assertEquals(updateTimestamp, repos[0].timestamp)
- assertRepoEquals(repo.copy(timestamp = updateTimestamp), repos[0])
- }
+ // fetch repos again and check that the result is as expected
+ repos = repoDao.getRepositories().sortedBy { it.repoId }
+ assertEquals(2, repos.size)
+ assertEquals(repoId, repos[0].repoId)
+ assertEquals(updateTimestamp, repos[0].timestamp)
+ assertRepoEquals(repo.copy(timestamp = updateTimestamp), repos[0])
+ }
- @Test
- fun mirrorDiff() {
- val repo = getRandomRepo()
- val updateMirrors = repo.mirrors.toMutableList().apply {
- removeLastOrNull()
- add(getRandomMirror())
- add(getRandomMirror())
- }
- val json = """
+ @Test
+ fun mirrorDiff() {
+ val repo = getRandomRepo()
+ val updateMirrors =
+ repo.mirrors.toMutableList().apply {
+ removeLastOrNull()
+ add(getRandomMirror())
+ add(getRandomMirror())
+ }
+ val json =
+ """
{
"mirrors": ${Json.encodeToString(updateMirrors)}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedMirrors = updateMirrors.map { mirror ->
- mirror.toMirror(repos[0].repoId)
- }.toSet()
- assertEquals(expectedMirrors, repos[0].mirrors.toSet())
- assertRepoEquals(repo.copy(mirrors = updateMirrors), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedMirrors = updateMirrors.map { mirror -> mirror.toMirror(repos[0].repoId) }.toSet()
+ assertEquals(expectedMirrors, repos[0].mirrors.toSet())
+ assertRepoEquals(repo.copy(mirrors = updateMirrors), repos[0])
}
+ }
- @Test
- fun mirrorUnknownKeyDiff() {
- val repo = getRandomRepo()
- val json = """
- {
- "mirrors": [
- { "url": "foo", "countryCode": "bar", "unknown": "doesntexist" }
- ]
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedMirrors = setOf(Mirror(repos[0].repoId, "foo", "bar"))
- assertEquals(expectedMirrors, repos[0].mirrors.toSet())
- assertRepoEquals(repo.copy(mirrors = listOf(MirrorV2("foo", "bar"))), repos[0])
- }
+ @Test
+ fun mirrorUnknownKeyDiff() {
+ val repo = getRandomRepo()
+ val json =
+ """
+ {
+ "mirrors": [
+ { "url": "foo", "countryCode": "bar", "unknown": "doesntexist" }
+ ]
+ }
+ """
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedMirrors = setOf(Mirror(repos[0].repoId, "foo", "bar"))
+ assertEquals(expectedMirrors, repos[0].mirrors.toSet())
+ assertRepoEquals(repo.copy(mirrors = listOf(MirrorV2("foo", "bar"))), repos[0])
}
+ }
- @Test
- fun descriptionDiff() {
- val repo = getRandomRepo().copy(description = mapOf("de" to "foo", "en" to "bar"))
- val updateText = if (Random.nextBoolean()) mapOf("de" to null, "en" to "foo") else null
- val json = """
+ @Test
+ fun descriptionDiff() {
+ val repo = getRandomRepo().copy(description = mapOf("de" to "foo", "en" to "bar"))
+ val updateText = if (Random.nextBoolean()) mapOf("de" to null, "en" to "foo") else null
+ val json =
+ """
{
"description": ${Json.encodeToString(updateText)}
- }""".trimIndent()
- val expectedText = if (updateText == null) emptyMap() else mapOf("en" to "foo")
- testDiff(repo, json) { repos ->
- assertEquals(expectedText, repos[0].repository.description)
- assertRepoEquals(repo.copy(description = expectedText), repos[0])
- }
+ }"""
+ .trimIndent()
+ val expectedText = if (updateText == null) emptyMap() else mapOf("en" to "foo")
+ testDiff(repo, json) { repos ->
+ assertEquals(expectedText, repos[0].repository.description)
+ assertRepoEquals(repo.copy(description = expectedText), repos[0])
}
+ }
- @Test
- fun antiFeaturesDiff() {
- val repo = getRandomRepo().copy(antiFeatures = getRandomMap {
- getRandomString() to AntiFeatureV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- })
- val antiFeatures = repo.antiFeatures.randomDiff {
- AntiFeatureV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- }
- val json = """
+ @Test
+ fun antiFeaturesDiff() {
+ val repo =
+ getRandomRepo()
+ .copy(
+ antiFeatures =
+ getRandomMap {
+ getRandomString() to
+ AntiFeatureV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ )
+ val antiFeatures =
+ repo.antiFeatures.randomDiff {
+ AntiFeatureV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ val json =
+ """
{
"antiFeatures": ${Json.encodeToString(antiFeatures)}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedFeatures = repo.antiFeatures.applyDiff(antiFeatures)
- val expectedRepoAntiFeatures =
- expectedFeatures.toRepoAntiFeatures(repos[0].repoId)
- assertEquals(expectedRepoAntiFeatures.toSet(), repos[0].antiFeatures.toSet())
- assertRepoEquals(repo.copy(antiFeatures = expectedFeatures), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedFeatures = repo.antiFeatures.applyDiff(antiFeatures)
+ val expectedRepoAntiFeatures = expectedFeatures.toRepoAntiFeatures(repos[0].repoId)
+ assertEquals(expectedRepoAntiFeatures.toSet(), repos[0].antiFeatures.toSet())
+ assertRepoEquals(repo.copy(antiFeatures = expectedFeatures), repos[0])
}
+ }
- @Test
- fun antiFeatureKeyChangeDiff() {
- val antiFeatureKey = getRandomString()
- val antiFeature = AntiFeatureV2(
- icon = getRandomLocalizedFileV2(),
+ @Test
+ fun antiFeatureKeyChangeDiff() {
+ val antiFeatureKey = getRandomString()
+ val antiFeature =
+ AntiFeatureV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ val antiFeatures = mapOf(antiFeatureKey to antiFeature)
+ val repo = getRandomRepo().copy(antiFeatures = antiFeatures)
+
+ @Suppress("UNCHECKED_CAST")
+ val newAntiFeatures =
+ mapOf(
+ antiFeatureKey to
+ antiFeature.copy(
+ icon = emptyMap(),
name = getRandomLocalizedTextV2(),
description = getRandomLocalizedTextV2(),
- )
- val antiFeatures = mapOf(antiFeatureKey to antiFeature)
- val repo = getRandomRepo().copy(antiFeatures = antiFeatures)
-
- @Suppress("UNCHECKED_CAST")
- val newAntiFeatures = mapOf(
- antiFeatureKey to antiFeature.copy(
- icon = emptyMap(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- )
- val json = """
+ )
+ )
+ val json =
+ """
{
"antiFeatures": {
"$antiFeatureKey": ${Json.encodeToString(newAntiFeatures)}
}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedFeatures = repo.antiFeatures.applyDiff(antiFeatures)
- val expectedRepoAntiFeatures =
- expectedFeatures.toRepoAntiFeatures(repos[0].repoId)
- assertEquals(expectedRepoAntiFeatures.toSet(), repos[0].antiFeatures.toSet())
- assertRepoEquals(repo.copy(antiFeatures = expectedFeatures), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedFeatures = repo.antiFeatures.applyDiff(antiFeatures)
+ val expectedRepoAntiFeatures = expectedFeatures.toRepoAntiFeatures(repos[0].repoId)
+ assertEquals(expectedRepoAntiFeatures.toSet(), repos[0].antiFeatures.toSet())
+ assertRepoEquals(repo.copy(antiFeatures = expectedFeatures), repos[0])
}
+ }
- @Test
- fun antiFeaturesRemovedOptionalsDiff() {
- val antiFeatureKey = getRandomString()
- val antiFeature = AntiFeatureV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- val antiFeatures = mapOf(antiFeatureKey to antiFeature)
- val repo = getRandomRepo().copy(antiFeatures = antiFeatures)
- val json = """
+ @Test
+ fun antiFeaturesRemovedOptionalsDiff() {
+ val antiFeatureKey = getRandomString()
+ val antiFeature =
+ AntiFeatureV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ val antiFeatures = mapOf(antiFeatureKey to antiFeature)
+ val repo = getRandomRepo().copy(antiFeatures = antiFeatures)
+ val json =
+ """
{
"antiFeatures": {
"$antiFeatureKey": {
@@ -212,54 +234,70 @@ internal class RepositoryDiffTest : DbTest() {
"description": null
}
}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- // icon and description maps were emptied (not nulled as they aren't nullable)
- assertEquals(emptyMap(), repos[0].getAntiFeatures()[antiFeatureKey]!!.icon)
- assertEquals(emptyMap(), repos[0].getAntiFeatures()[antiFeatureKey]!!.description)
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ // icon and description maps were emptied (not nulled as they aren't nullable)
+ assertEquals(emptyMap(), repos[0].getAntiFeatures()[antiFeatureKey]!!.icon)
+ assertEquals(emptyMap(), repos[0].getAntiFeatures()[antiFeatureKey]!!.description)
}
+ }
- @Test
- fun categoriesDiff() {
- val repo = getRandomRepo().copy(categories = getRandomMap {
- getRandomString() to CategoryV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- })
- val categories = repo.categories.randomDiff {
- CategoryV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- }
- val json = """
+ @Test
+ fun categoriesDiff() {
+ val repo =
+ getRandomRepo()
+ .copy(
+ categories =
+ getRandomMap {
+ getRandomString() to
+ CategoryV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ )
+ val categories =
+ repo.categories.randomDiff {
+ CategoryV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ val json =
+ """
{
"categories": ${Json.encodeToString(categories)}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedCategories = repo.categories.applyDiff(categories)
- val expectedRepoCategories =
- expectedCategories.toRepoCategories(repos[0].repoId)
- assertEquals(expectedRepoCategories.toSet(), repos[0].categories.toSet())
- assertRepoEquals(repo.copy(categories = expectedCategories), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedCategories = repo.categories.applyDiff(categories)
+ val expectedRepoCategories = expectedCategories.toRepoCategories(repos[0].repoId)
+ assertEquals(expectedRepoCategories.toSet(), repos[0].categories.toSet())
+ assertRepoEquals(repo.copy(categories = expectedCategories), repos[0])
}
+ }
- @Test
- fun categoriesRemovedOptionalsDiff() {
- val categoryId = getRandomString()
- val repo = getRandomRepo().copy(categories = getRandomMap {
- categoryId to CategoryV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- })
- val json = """
+ @Test
+ fun categoriesRemovedOptionalsDiff() {
+ val categoryId = getRandomString()
+ val repo =
+ getRandomRepo()
+ .copy(
+ categories =
+ getRandomMap {
+ categoryId to
+ CategoryV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ )
+ val json =
+ """
{
"categories": {
"$categoryId": {
@@ -267,48 +305,64 @@ internal class RepositoryDiffTest : DbTest() {
"description": null
}
}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- // icon and description maps were emptied (not nulled as they aren't nullable)
- assertEquals(emptyMap(), repos[0].getCategories()[categoryId]!!.icon)
- assertEquals(emptyMap(), repos[0].getCategories()[categoryId]!!.description)
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ // icon and description maps were emptied (not nulled as they aren't nullable)
+ assertEquals(emptyMap(), repos[0].getCategories()[categoryId]!!.icon)
+ assertEquals(emptyMap(), repos[0].getCategories()[categoryId]!!.description)
}
+ }
- @Test
- fun releaseChannelsDiff() {
- val repo = getRandomRepo().copy(releaseChannels = getRandomMap {
- getRandomString() to ReleaseChannelV2(
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- })
- val releaseChannels = repo.releaseChannels.randomDiff {
- ReleaseChannelV2(getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
- }
- val json = """
+ @Test
+ fun releaseChannelsDiff() {
+ val repo =
+ getRandomRepo()
+ .copy(
+ releaseChannels =
+ getRandomMap {
+ getRandomString() to
+ ReleaseChannelV2(
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ )
+ val releaseChannels =
+ repo.releaseChannels.randomDiff {
+ ReleaseChannelV2(getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
+ }
+ val json =
+ """
{
"releaseChannels": ${Json.encodeToString(releaseChannels)}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- val expectedFeatures = repo.releaseChannels.applyDiff(releaseChannels)
- val expectedRepoReleaseChannels =
- expectedFeatures.toRepoReleaseChannel(repos[0].repoId)
- assertEquals(expectedRepoReleaseChannels.toSet(), repos[0].releaseChannels.toSet())
- assertRepoEquals(repo.copy(releaseChannels = expectedFeatures), repos[0])
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ val expectedFeatures = repo.releaseChannels.applyDiff(releaseChannels)
+ val expectedRepoReleaseChannels = expectedFeatures.toRepoReleaseChannel(repos[0].repoId)
+ assertEquals(expectedRepoReleaseChannels.toSet(), repos[0].releaseChannels.toSet())
+ assertRepoEquals(repo.copy(releaseChannels = expectedFeatures), repos[0])
}
+ }
- @Test
- fun releaseChannelsRemovedOptionalsDiff() {
- val releaseChannelKey = getRandomString()
- val repo = getRandomRepo().copy(releaseChannels = getRandomMap {
- releaseChannelKey to ReleaseChannelV2(
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
- )
- })
- val json = """
+ @Test
+ fun releaseChannelsRemovedOptionalsDiff() {
+ val releaseChannelKey = getRandomString()
+ val repo =
+ getRandomRepo()
+ .copy(
+ releaseChannels =
+ getRandomMap {
+ releaseChannelKey to
+ ReleaseChannelV2(
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
+ )
+ }
+ )
+ val json =
+ """
{
"releaseChannels": {
"$releaseChannelKey": {
@@ -316,88 +370,99 @@ internal class RepositoryDiffTest : DbTest() {
"icon": null
}
}
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- // icon and description maps were emptied (not nulled as they aren't nullable)
- assertEquals(emptyMap(), repos[0].getReleaseChannels()[releaseChannelKey]!!.icon)
- assertEquals(emptyMap(), repos[0].getReleaseChannels()[releaseChannelKey]!!.description)
- }
+ }"""
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ // icon and description maps were emptied (not nulled as they aren't nullable)
+ assertEquals(emptyMap(), repos[0].getReleaseChannels()[releaseChannelKey]!!.icon)
+ assertEquals(emptyMap(), repos[0].getReleaseChannels()[releaseChannelKey]!!.description)
}
+ }
- @Test
- fun removeAllOptionalsDiff() {
- val repo = getRandomRepo().copy(
- // ensure optional values exist
- name = getRandomLocalizedTextV2(2),
- webBaseUrl = getRandomString(),
- mirrors = getRandomList(2) { getRandomMirror() },
- antiFeatures = getRandomMap(2) {
- getRandomString() to AntiFeatureV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
+ @Test
+ fun removeAllOptionalsDiff() {
+ val repo =
+ getRandomRepo()
+ .copy(
+ // ensure optional values exist
+ name = getRandomLocalizedTextV2(2),
+ webBaseUrl = getRandomString(),
+ mirrors = getRandomList(2) { getRandomMirror() },
+ antiFeatures =
+ getRandomMap(2) {
+ getRandomString() to
+ AntiFeatureV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
)
},
- categories = getRandomMap(2) {
- getRandomString() to CategoryV2(
- icon = getRandomLocalizedFileV2(),
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
+ categories =
+ getRandomMap(2) {
+ getRandomString() to
+ CategoryV2(
+ icon = getRandomLocalizedFileV2(),
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
)
},
- releaseChannels = getRandomMap(2) {
- getRandomString() to ReleaseChannelV2(
- name = getRandomLocalizedTextV2(),
- description = getRandomLocalizedTextV2(),
+ releaseChannels =
+ getRandomMap(2) {
+ getRandomString() to
+ ReleaseChannelV2(
+ name = getRandomLocalizedTextV2(),
+ description = getRandomLocalizedTextV2(),
)
},
)
- assertTrue(repo.name.isNotEmpty())
- assertTrue(repo.icon.isNotEmpty())
- assertTrue(!repo.webBaseUrl.isNullOrEmpty())
- assertTrue(repo.mirrors.isNotEmpty())
- assertTrue(repo.antiFeatures.isNotEmpty())
- assertTrue(repo.categories.isNotEmpty())
- assertTrue(repo.releaseChannels.isNotEmpty())
- val json = """
- {
- "name": null,
- "icon": null,
- "webBaseUrl": null,
- "mirrors": null,
- "antiFeatures": null,
- "categories": null,
- "releaseChannels": null
- }""".trimIndent()
- testDiff(repo, json) { repos ->
- assertEquals(emptyMap(), repos[0].repository.name)
- assertEquals(null, repos[0].repository.icon)
- assertEquals(null, repos[0].webBaseUrl)
- assertEquals(emptyList(), repos[0].mirrors)
- assertEquals(emptyMap(), repos[0].getAntiFeatures())
- assertEquals(emptyMap(), repos[0].getCategories())
- assertEquals(emptyMap(), repos[0].getReleaseChannels())
- }
+ assertTrue(repo.name.isNotEmpty())
+ assertTrue(repo.icon.isNotEmpty())
+ assertTrue(!repo.webBaseUrl.isNullOrEmpty())
+ assertTrue(repo.mirrors.isNotEmpty())
+ assertTrue(repo.antiFeatures.isNotEmpty())
+ assertTrue(repo.categories.isNotEmpty())
+ assertTrue(repo.releaseChannels.isNotEmpty())
+ val json =
+ """
+ {
+ "name": null,
+ "icon": null,
+ "webBaseUrl": null,
+ "mirrors": null,
+ "antiFeatures": null,
+ "categories": null,
+ "releaseChannels": null
+ }
+ """
+ .trimIndent()
+ testDiff(repo, json) { repos ->
+ assertEquals(emptyMap(), repos[0].repository.name)
+ assertEquals(null, repos[0].repository.icon)
+ assertEquals(null, repos[0].webBaseUrl)
+ assertEquals(emptyList(), repos[0].mirrors)
+ assertEquals(emptyMap(), repos[0].getAntiFeatures())
+ assertEquals(emptyMap(), repos[0].getCategories())
+ assertEquals(emptyMap(), repos[0].getReleaseChannels())
}
+ }
- private fun testDiff(repo: RepoV2, json: String, repoChecker: (List) -> Unit) {
- // insert repo
- repoDao.insertOrReplace(repo)
+ private fun testDiff(repo: RepoV2, json: String, repoChecker: (List) -> Unit) {
+ // insert repo
+ repoDao.insertOrReplace(repo)
- // check that the repo got added and retrieved as expected
- var repos = repoDao.getRepositories()
- assertEquals(1, repos.size)
- val repoId = repos[0].repoId
+ // check that the repo got added and retrieved as expected
+ var repos = repoDao.getRepositories()
+ assertEquals(1, repos.size)
+ val repoId = repos[0].repoId
- // decode diff from JSON and update DB with it
- val diff = j.parseToJsonElement(json).jsonObject
- repoDao.updateRepository(repoId, 42, diff)
-
- // fetch repos again and check that the result is as expected
- repos = repoDao.getRepositories().sortedBy { it.repoId }
- assertEquals(1, repos.size)
- assertEquals(repoId, repos[0].repoId)
- repoChecker(repos)
- }
+ // decode diff from JSON and update DB with it
+ val diff = j.parseToJsonElement(json).jsonObject
+ repoDao.updateRepository(repoId, 42, diff)
+ // fetch repos again and check that the result is as expected
+ repos = repoDao.getRepositories().sortedBy { it.repoId }
+ assertEquals(1, repos.size)
+ assertEquals(repoId, repos[0].repoId)
+ repoChecker(repos)
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt b/libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt
index 745906c67..0933a51b7 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt
@@ -2,119 +2,126 @@ package org.fdroid.database
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
-import org.fdroid.index.v2.FeatureV2
-import org.fdroid.index.v2.ManifestV2
-import org.fdroid.index.v2.MetadataV2
-import org.fdroid.index.v2.PackageVersionV2
-import org.fdroid.index.v2.RepoV2
-import org.junit.Assert
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
import kotlin.test.fail
+import org.fdroid.index.v2.FeatureV2
+import org.fdroid.index.v2.ManifestV2
+import org.fdroid.index.v2.MetadataV2
+import org.fdroid.index.v2.PackageVersionV2
+import org.fdroid.index.v2.RepoV2
+import org.junit.Assert
internal object TestUtils {
- fun assertTimestampRecent(timestamp: Long?) {
- assertNotNull(timestamp)
- assertTrue(System.currentTimeMillis() - timestamp < 2000)
- }
+ fun assertTimestampRecent(timestamp: Long?) {
+ assertNotNull(timestamp)
+ assertTrue(System.currentTimeMillis() - timestamp < 2000)
+ }
- fun assertRepoEquals(repoV2: RepoV2, repo: Repository) {
- val repoId = repo.repoId
- // mirrors
- val expectedMirrors = repoV2.mirrors.toMirrors(repoId).toSet()
- Assert.assertEquals(expectedMirrors, repo.mirrors.toSet())
- // anti-features
- val expectedAntiFeatures = repoV2.antiFeatures.toRepoAntiFeatures(repoId).toSet()
- assertEquals(expectedAntiFeatures, repo.antiFeatures.toSet())
- // categories
- val expectedCategories = repoV2.categories.toRepoCategories(repoId).sortedBy { it.id }
- assertEquals(expectedCategories, repo.categories.sortedBy { it.id })
- // release channels
- val expectedReleaseChannels = repoV2.releaseChannels.toRepoReleaseChannel(repoId).toSet()
- assertEquals(expectedReleaseChannels, repo.releaseChannels.toSet())
- // core repo
- val coreRepo = repoV2.toCoreRepository(
- version = repo.repository.version!!.toLong(),
- formatVersion = repo.repository.formatVersion,
- certificate = repo.repository.certificate,
- ).copy(repoId = repoId)
- assertEquals(coreRepo, repo.repository)
- }
+ fun assertRepoEquals(repoV2: RepoV2, repo: Repository) {
+ val repoId = repo.repoId
+ // mirrors
+ val expectedMirrors = repoV2.mirrors.toMirrors(repoId).toSet()
+ Assert.assertEquals(expectedMirrors, repo.mirrors.toSet())
+ // anti-features
+ val expectedAntiFeatures = repoV2.antiFeatures.toRepoAntiFeatures(repoId).toSet()
+ assertEquals(expectedAntiFeatures, repo.antiFeatures.toSet())
+ // categories
+ val expectedCategories = repoV2.categories.toRepoCategories(repoId).sortedBy { it.id }
+ assertEquals(expectedCategories, repo.categories.sortedBy { it.id })
+ // release channels
+ val expectedReleaseChannels = repoV2.releaseChannels.toRepoReleaseChannel(repoId).toSet()
+ assertEquals(expectedReleaseChannels, repo.releaseChannels.toSet())
+ // core repo
+ val coreRepo =
+ repoV2
+ .toCoreRepository(
+ version = repo.repository.version!!.toLong(),
+ formatVersion = repo.repository.formatVersion,
+ certificate = repo.repository.certificate,
+ )
+ .copy(repoId = repoId)
+ assertEquals(coreRepo, repo.repository)
+ }
- internal fun App.toMetadataV2() = MetadataV2(
- added = metadata.added,
- lastUpdated = metadata.lastUpdated,
- name = metadata.name,
- summary = metadata.summary,
- description = metadata.description,
- webSite = metadata.webSite,
- changelog = metadata.changelog,
- license = metadata.license,
- sourceCode = metadata.sourceCode,
- issueTracker = metadata.issueTracker,
- translation = metadata.translation,
- preferredSigner = metadata.preferredSigner,
- video = metadata.video,
- authorName = metadata.authorName,
- authorEmail = metadata.authorEmail,
- authorWebSite = metadata.authorWebSite,
- authorPhone = metadata.authorPhone,
- donate = metadata.donate ?: emptyList(),
- liberapayID = metadata.liberapayID,
- liberapay = metadata.liberapay,
- openCollective = metadata.openCollective,
- bitcoin = metadata.bitcoin,
- litecoin = metadata.litecoin,
- flattrID = metadata.flattrID,
- categories = metadata.categories ?: emptyList(),
- icon = icon,
- featureGraphic = featureGraphic,
- promoGraphic = promoGraphic,
- tvBanner = tvBanner,
- screenshots = screenshots,
+ internal fun App.toMetadataV2() =
+ MetadataV2(
+ added = metadata.added,
+ lastUpdated = metadata.lastUpdated,
+ name = metadata.name,
+ summary = metadata.summary,
+ description = metadata.description,
+ webSite = metadata.webSite,
+ changelog = metadata.changelog,
+ license = metadata.license,
+ sourceCode = metadata.sourceCode,
+ issueTracker = metadata.issueTracker,
+ translation = metadata.translation,
+ preferredSigner = metadata.preferredSigner,
+ video = metadata.video,
+ authorName = metadata.authorName,
+ authorEmail = metadata.authorEmail,
+ authorWebSite = metadata.authorWebSite,
+ authorPhone = metadata.authorPhone,
+ donate = metadata.donate ?: emptyList(),
+ liberapayID = metadata.liberapayID,
+ liberapay = metadata.liberapay,
+ openCollective = metadata.openCollective,
+ bitcoin = metadata.bitcoin,
+ litecoin = metadata.litecoin,
+ flattrID = metadata.flattrID,
+ categories = metadata.categories ?: emptyList(),
+ icon = icon,
+ featureGraphic = featureGraphic,
+ promoGraphic = promoGraphic,
+ tvBanner = tvBanner,
+ screenshots = screenshots,
)
- fun AppVersion.toPackageVersionV2() = PackageVersionV2(
- added = added,
- file = file,
- src = src,
- manifest = ManifestV2(
- versionName = manifest.versionName,
- versionCode = manifest.versionCode,
- usesSdk = manifest.usesSdk,
- maxSdkVersion = manifest.maxSdkVersion,
- signer = manifest.signer,
- usesPermission = usesPermission.sortedBy { it.name },
- usesPermissionSdk23 = usesPermissionSdk23.sortedBy { it.name },
- nativecode = manifest.nativecode?.sorted() ?: emptyList(),
- features = manifest.features?.map { FeatureV2(it) } ?: emptyList(),
+ fun AppVersion.toPackageVersionV2() =
+ PackageVersionV2(
+ added = added,
+ file = file,
+ src = src,
+ manifest =
+ ManifestV2(
+ versionName = manifest.versionName,
+ versionCode = manifest.versionCode,
+ usesSdk = manifest.usesSdk,
+ maxSdkVersion = manifest.maxSdkVersion,
+ signer = manifest.signer,
+ usesPermission = usesPermission.sortedBy { it.name },
+ usesPermissionSdk23 = usesPermissionSdk23.sortedBy { it.name },
+ nativecode = manifest.nativecode?.sorted() ?: emptyList(),
+ features = manifest.features?.map { FeatureV2(it) } ?: emptyList(),
),
- releaseChannels = releaseChannels,
- antiFeatures = version.antiFeatures ?: emptyMap(),
- whatsNew = version.whatsNew ?: emptyMap(),
+ releaseChannels = releaseChannels,
+ antiFeatures = version.antiFeatures ?: emptyMap(),
+ whatsNew = version.whatsNew ?: emptyMap(),
)
- fun LiveData.getOrAwaitValue(): T? {
- val data = arrayOfNulls(1)
- val latch = CountDownLatch(1)
- val observer: Observer = object : Observer {
- override fun onChanged(value: T) {
- data[0] = value
- latch.countDown()
- removeObserver(this)
- }
+ fun LiveData.getOrAwaitValue(): T? {
+ val data = arrayOfNulls(1)
+ val latch = CountDownLatch(1)
+ val observer: Observer =
+ object : Observer {
+ override fun onChanged(value: T) {
+ data[0] = value
+ latch.countDown()
+ removeObserver(this)
}
- observeForever(observer)
- latch.await(2, TimeUnit.SECONDS)
- @Suppress("UNCHECKED_CAST")
- return data[0] as T?
- }
+ }
+ observeForever(observer)
+ latch.await(2, TimeUnit.SECONDS)
+ @Suppress("UNCHECKED_CAST")
+ return data[0] as T?
+ }
- fun LiveData.getOrFail(): T {
- return getOrAwaitValue() ?: fail()
- }
+ fun LiveData.getOrFail(): T {
+ return getOrAwaitValue() ?: fail()
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt b/libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt
index bbf2b3174..4b1ddf910 100644
--- a/libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt
@@ -1,6 +1,9 @@
package org.fdroid.database
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.random.Random
+import kotlin.test.assertEquals
+import kotlin.test.fail
import org.fdroid.database.TestUtils.getOrFail
import org.fdroid.index.v2.PackageVersionV2
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
@@ -9,255 +12,253 @@ import org.fdroid.test.TestUtils.getRandomString
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.random.Random
-import kotlin.test.assertEquals
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class VersionTest : DbTest() {
- private val packageName = getRandomString()
- private val packageVersion1 = getRandomPackageVersionV2()
- private val packageVersion2 = getRandomPackageVersionV2()
- private val packageVersion3 = getRandomPackageVersionV2()
- private val versionId1 = packageVersion1.file.sha256
- private val versionId2 = packageVersion2.file.sha256
- private val versionId3 = packageVersion3.file.sha256
- private val isCompatible1 = Random.nextBoolean()
- private val isCompatible2 = Random.nextBoolean()
- private val packageVersions = mapOf(
- versionId1 to packageVersion1,
- versionId2 to packageVersion2,
+ private val packageName = getRandomString()
+ private val packageVersion1 = getRandomPackageVersionV2()
+ private val packageVersion2 = getRandomPackageVersionV2()
+ private val packageVersion3 = getRandomPackageVersionV2()
+ private val versionId1 = packageVersion1.file.sha256
+ private val versionId2 = packageVersion2.file.sha256
+ private val versionId3 = packageVersion3.file.sha256
+ private val isCompatible1 = Random.nextBoolean()
+ private val isCompatible2 = Random.nextBoolean()
+ private val packageVersions = mapOf(versionId1 to packageVersion1, versionId2 to packageVersion2)
+
+ private fun getAppVersion1(repoId: Long): AppVersion {
+ val version = getVersion1(repoId)
+ return AppVersion(
+ version = version,
+ versionedStrings = packageVersion1.manifest.getVersionedStrings(version),
)
+ }
- private fun getAppVersion1(repoId: Long): AppVersion {
- val version = getVersion1(repoId)
- return AppVersion(
- version = version,
- versionedStrings = packageVersion1.manifest.getVersionedStrings(version),
- )
+ private fun getAppVersion2(repoId: Long): AppVersion {
+ val version = getVersion2(repoId)
+ return AppVersion(
+ version = version,
+ versionedStrings = packageVersion2.manifest.getVersionedStrings(version),
+ )
+ }
+
+ private fun getVersion1(repoId: Long) =
+ packageVersion1.toVersion(repoId, packageName, versionId1, isCompatible1)
+
+ private fun getVersion2(repoId: Long) =
+ packageVersion2.toVersion(repoId, packageName, versionId2, isCompatible2)
+
+ private val compatChecker: (PackageVersionV2) -> Boolean = {
+ when (it.file.sha256) {
+ versionId1 -> isCompatible1
+ versionId2 -> isCompatible2
+ else -> fail()
}
+ }
- private fun getAppVersion2(repoId: Long): AppVersion {
- val version = getVersion2(repoId)
- return AppVersion(
- version = version,
- versionedStrings = packageVersion2.manifest.getVersionedStrings(version),
- )
- }
+ @Test
+ fun insertGetDeleteSingleVersion() {
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, versionId1, packageVersion1, isCompatible1)
- private fun getVersion1(repoId: Long) =
- packageVersion1.toVersion(repoId, packageName, versionId1, isCompatible1)
+ val appVersions = versionDao.getAppVersions(repoId, packageName).getOrFail()
+ assertEquals(1, appVersions.size)
+ assertEquals(getAppVersion1(repoId), appVersions[0])
- private fun getVersion2(repoId: Long) =
- packageVersion2.toVersion(repoId, packageName, versionId2, isCompatible2)
+ val manifest = packageVersion1.manifest
+ val versionedStrings = versionDao.getVersionedStrings(repoId, packageName)
+ val expectedSize = manifest.usesPermission.size + manifest.usesPermissionSdk23.size
+ assertEquals(expectedSize, versionedStrings.size)
- private val compatChecker: (PackageVersionV2) -> Boolean = {
- when (it.file.sha256) {
- versionId1 -> isCompatible1
- versionId2 -> isCompatible2
- else -> fail()
- }
- }
+ // getting version by repo produces same result
+ val versionsByRepo = versionDao.getAppVersions(repoId, packageName).getOrFail()
+ assertEquals(1, versionsByRepo.size)
+ assertEquals(getAppVersion1(repoId), versionsByRepo[0])
- @Test
- fun insertGetDeleteSingleVersion() {
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, versionId1, packageVersion1, isCompatible1)
+ versionDao.deleteAppVersion(repoId, packageName, versionId1)
+ assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
+ assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
+ }
- val appVersions = versionDao.getAppVersions(repoId, packageName).getOrFail()
- assertEquals(1, appVersions.size)
- assertEquals(getAppVersion1(repoId), appVersions[0])
+ @Test
+ fun insertGetDeleteTwoVersions() {
+ // insert two versions along with required objects
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, versionId1, packageVersion1, isCompatible1)
+ versionDao.insert(repoId, packageName, versionId2, packageVersion2, isCompatible2)
- val manifest = packageVersion1.manifest
- val versionedStrings = versionDao.getVersionedStrings(repoId, packageName)
- val expectedSize = manifest.usesPermission.size + manifest.usesPermissionSdk23.size
- assertEquals(expectedSize, versionedStrings.size)
+ // get app versions from DB and assign them correctly
+ listOf(
+ versionDao.getAppVersions(packageName).getOrFail(),
+ versionDao.getAppVersions(repoId, packageName).getOrFail(),
+ )
+ .forEach { appVersions ->
+ assertEquals(2, appVersions.size)
+ val appVersion =
+ if (versionId1 == appVersions[0].version.versionId) {
+ appVersions[0]
+ } else appVersions[1]
+ val appVersion2 =
+ if (versionId2 == appVersions[0].version.versionId) {
+ appVersions[0]
+ } else appVersions[1]
- // getting version by repo produces same result
- val versionsByRepo = versionDao.getAppVersions(repoId, packageName).getOrFail()
- assertEquals(1, versionsByRepo.size)
- assertEquals(getAppVersion1(repoId), versionsByRepo[0])
+ // check first version matches
+ assertEquals(getAppVersion1(repoId), appVersion)
- versionDao.deleteAppVersion(repoId, packageName, versionId1)
- assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
- assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
- }
+ // check second version matches
+ assertEquals(getAppVersion2(repoId), appVersion2)
+ }
- @Test
- fun insertGetDeleteTwoVersions() {
- // insert two versions along with required objects
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, versionId1, packageVersion1, isCompatible1)
- versionDao.insert(repoId, packageName, versionId2, packageVersion2, isCompatible2)
+ // delete app and check that all associated data also gets deleted
+ appDao.deleteAppMetadata(repoId, packageName)
+ assertEquals(0, versionDao.getAppVersions(packageName).getOrFail().size)
+ assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
+ assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
+ }
- // get app versions from DB and assign them correctly
- listOf(
- versionDao.getAppVersions(packageName).getOrFail(),
- versionDao.getAppVersions(repoId, packageName).getOrFail(),
- ).forEach { appVersions ->
- assertEquals(2, appVersions.size)
- val appVersion = if (versionId1 == appVersions[0].version.versionId) {
- appVersions[0]
- } else appVersions[1]
- val appVersion2 = if (versionId2 == appVersions[0].version.versionId) {
- appVersions[0]
- } else appVersions[1]
+ @Test
+ fun versionsOnlyFromEnabledRepo() {
+ // insert two versions into the same repo
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, packageVersions, compatChecker)
+ assertEquals(2, versionDao.getAppVersions(packageName).getOrFail().size)
+ assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
- // check first version matches
- assertEquals(getAppVersion1(repoId), appVersion)
+ // add another version into another repo
+ val repoId2 = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId2, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId2, packageName, versionId3, packageVersion3, true)
+ assertEquals(3, versionDao.getAppVersions(packageName).getOrFail().size)
+ assertEquals(3, versionDao.getVersions(listOf(packageName)).size)
- // check second version matches
- assertEquals(getAppVersion2(repoId), appVersion2)
- }
+ // query by repo only returns the versions from each repo
+ assertEquals(2, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
+ assertEquals(1, versionDao.getAppVersions(repoId2, packageName).getOrFail().size)
- // delete app and check that all associated data also gets deleted
- appDao.deleteAppMetadata(repoId, packageName)
- assertEquals(0, versionDao.getAppVersions(packageName).getOrFail().size)
- assertEquals(0, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
- assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
- }
+ // disable second repo
+ repoDao.setRepositoryEnabled(repoId2, false)
- @Test
- fun versionsOnlyFromEnabledRepo() {
- // insert two versions into the same repo
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, packageVersions, compatChecker)
- assertEquals(2, versionDao.getAppVersions(packageName).getOrFail().size)
- assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
+ // now only two versions get returned
+ assertEquals(2, versionDao.getAppVersions(packageName).getOrFail().size)
+ assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
+ }
- // add another version into another repo
- val repoId2 = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId2, packageName, getRandomMetadataV2())
- versionDao.insert(repoId2, packageName, versionId3, packageVersion3, true)
- assertEquals(3, versionDao.getAppVersions(packageName).getOrFail().size)
- assertEquals(3, versionDao.getVersions(listOf(packageName)).size)
+ @Test
+ fun versionsSortedByVersionCode() {
+ // insert three versions into the same repo
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, packageVersions, compatChecker)
+ versionDao.insert(repoId, packageName, versionId3, packageVersion3, true)
+ val versions1 = versionDao.getAppVersions(packageName).getOrFail()
+ val versions2 = versionDao.getVersions(listOf(packageName))
+ val versions3 = versionDao.getAppVersions(repoId, packageName).getOrFail()
+ assertEquals(3, versions1.size)
+ assertEquals(3, versions2.size)
+ assertEquals(3, versions3.size)
- // query by repo only returns the versions from each repo
- assertEquals(2, versionDao.getAppVersions(repoId, packageName).getOrFail().size)
- assertEquals(1, versionDao.getAppVersions(repoId2, packageName).getOrFail().size)
+ // check that they are sorted as expected
+ listOf(
+ packageVersion1.manifest.versionCode,
+ packageVersion2.manifest.versionCode,
+ packageVersion3.manifest.versionCode,
+ )
+ .sortedDescending()
+ .forEachIndexed { i, versionCode ->
+ assertEquals(versionCode, versions1[i].version.manifest.versionCode)
+ assertEquals(versionCode, versions2[i].versionCode)
+ assertEquals(versionCode, versions3[i].version.manifest.versionCode)
+ }
+ }
- // disable second repo
- repoDao.setRepositoryEnabled(repoId2, false)
+ @Test
+ fun getVersionsRespectsAppPrefsIgnore() {
+ // insert one version into the repo
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ val versionCode = Random.nextLong(1, Long.MAX_VALUE)
+ val packageVersion = getRandomPackageVersionV2(versionCode)
+ val versionId = packageVersion.file.sha256
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, versionId, packageVersion, true)
+ assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
- // now only two versions get returned
- assertEquals(2, versionDao.getAppVersions(packageName).getOrFail().size)
- assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
- }
+ // default app prefs don't change result
+ var appPrefs = AppPrefs(packageName)
+ appPrefsDao.update(appPrefs)
+ assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
- @Test
- fun versionsSortedByVersionCode() {
- // insert three versions into the same repo
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, packageVersions, compatChecker)
- versionDao.insert(repoId, packageName, versionId3, packageVersion3, true)
- val versions1 = versionDao.getAppVersions(packageName).getOrFail()
- val versions2 = versionDao.getVersions(listOf(packageName))
- val versions3 = versionDao.getAppVersions(repoId, packageName).getOrFail()
- assertEquals(3, versions1.size)
- assertEquals(3, versions2.size)
- assertEquals(3, versions3.size)
+ // ignore lower version code doesn't change result
+ appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode - 1)
+ appPrefsDao.update(appPrefs)
+ assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
- // check that they are sorted as expected
- listOf(
- packageVersion1.manifest.versionCode,
- packageVersion2.manifest.versionCode,
- packageVersion3.manifest.versionCode,
- ).sortedDescending().forEachIndexed { i, versionCode ->
- assertEquals(versionCode, versions1[i].version.manifest.versionCode)
- assertEquals(versionCode, versions2[i].versionCode)
- assertEquals(versionCode, versions3[i].version.manifest.versionCode)
- }
- }
+ // ignoring exact version code does change result
+ appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode)
+ appPrefsDao.update(appPrefs)
+ assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
- @Test
- fun getVersionsRespectsAppPrefsIgnore() {
- // insert one version into the repo
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- val versionCode = Random.nextLong(1, Long.MAX_VALUE)
- val packageVersion = getRandomPackageVersionV2(versionCode)
- val versionId = packageVersion.file.sha256
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, versionId, packageVersion, true)
- assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
+ // ignoring higher version code does change result
+ appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode + 1)
+ appPrefsDao.update(appPrefs)
+ assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
- // default app prefs don't change result
- var appPrefs = AppPrefs(packageName)
- appPrefsDao.update(appPrefs)
- assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
+ // ignoring all updates does change result
+ appPrefs = appPrefs.toggleIgnoreAllUpdates()
+ appPrefsDao.update(appPrefs)
+ assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
- // ignore lower version code doesn't change result
- appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode - 1)
- appPrefsDao.update(appPrefs)
- assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
+ // not ignoring all updates brings back version
+ appPrefs = appPrefs.toggleIgnoreAllUpdates()
+ appPrefsDao.update(appPrefs)
+ assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
- // ignoring exact version code does change result
- appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode)
- appPrefsDao.update(appPrefs)
- assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
+ // clear all apps and their versions
+ appDao.clearAll()
+ assertEquals(0, versionDao.countAppVersions())
+ assertEquals(0, versionDao.countVersionedStrings())
+ }
- // ignoring higher version code does change result
- appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode + 1)
- appPrefsDao.update(appPrefs)
- assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
+ @Test
+ fun getVersionsConsidersOnlyGivenPackages() {
+ // insert two versions
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, packageVersions, compatChecker)
+ assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
- // ignoring all updates does change result
- appPrefs = appPrefs.toggleIgnoreAllUpdates()
- appPrefsDao.update(appPrefs)
- assertEquals(0, versionDao.getVersions(listOf(packageName)).size)
+ // insert versions for a different package
+ val packageName2 = getRandomString()
+ appDao.insert(repoId, packageName2, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName2, packageVersions, compatChecker)
- // not ignoring all updates brings back version
- appPrefs = appPrefs.toggleIgnoreAllUpdates()
- appPrefsDao.update(appPrefs)
- assertEquals(1, versionDao.getVersions(listOf(packageName)).size)
+ // still only returns above versions
+ assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
- // clear all apps and their versions
- appDao.clearAll()
- assertEquals(0, versionDao.countAppVersions())
- assertEquals(0, versionDao.countVersionedStrings())
- }
+ // all versions are returned only if all packages are asked for
+ assertEquals(4, versionDao.getVersions(listOf(packageName, packageName2)).size)
+ }
- @Test
- fun getVersionsConsidersOnlyGivenPackages() {
- // insert two versions
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, packageVersions, compatChecker)
- assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
+ @Test
+ fun getVersionsHandlesMaxVariableNumber() {
+ // sqlite has a maximum number of 999 variables that can be used in a query
+ val packagesOk = MutableList(998) { "" } + listOf(packageName)
+ val packagesNotOk1 = MutableList(1000) { "" } + listOf(packageName)
+ val packagesNotOk2 = MutableList(5000) { "" } + listOf(packageName)
- // insert versions for a different package
- val packageName2 = getRandomString()
- appDao.insert(repoId, packageName2, getRandomMetadataV2())
- versionDao.insert(repoId, packageName2, packageVersions, compatChecker)
-
- // still only returns above versions
- assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
-
- // all versions are returned only if all packages are asked for
- assertEquals(4, versionDao.getVersions(listOf(packageName, packageName2)).size)
- }
-
- @Test
- fun getVersionsHandlesMaxVariableNumber() {
- // sqlite has a maximum number of 999 variables that can be used in a query
- val packagesOk = MutableList(998) { "" } + listOf(packageName)
- val packagesNotOk1 = MutableList(1000) { "" } + listOf(packageName)
- val packagesNotOk2 = MutableList(5000) { "" } + listOf(packageName)
-
- // insert two versions
- val repoId = repoDao.insertOrReplace(getRandomRepo())
- appDao.insert(repoId, packageName, getRandomMetadataV2())
- versionDao.insert(repoId, packageName, packageVersions, compatChecker)
- assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
-
- // versions are returned as expected for all lists, no matter their size
- assertEquals(2, versionDao.getVersions(packagesOk).size)
- assertEquals(2, versionDao.getVersions(packagesNotOk1).size)
- assertEquals(2, versionDao.getVersions(packagesNotOk2).size)
- }
+ // insert two versions
+ val repoId = repoDao.insertOrReplace(getRandomRepo())
+ appDao.insert(repoId, packageName, getRandomMetadataV2())
+ versionDao.insert(repoId, packageName, packageVersions, compatChecker)
+ assertEquals(2, versionDao.getVersions(listOf(packageName)).size)
+ // versions are returned as expected for all lists, no matter their size
+ assertEquals(2, versionDao.getVersions(packagesOk).size)
+ assertEquals(2, versionDao.getVersions(packagesNotOk1).size)
+ assertEquals(2, versionDao.getVersions(packagesNotOk2).size)
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt
index aff1da593..40b72de31 100644
--- a/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/index/IndexUpdaterTest.kt
@@ -1,29 +1,29 @@
package org.fdroid.index
import androidx.test.ext.junit.runners.AndroidJUnit4
+import kotlin.test.assertEquals
import org.fdroid.database.Repository
import org.fdroid.index.v2.SIGNED_FILE_NAME
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
@RunWith(AndroidJUnit4::class)
internal class IndexUpdaterTest {
- @Test
- fun testDefaultUriBuilder() {
- val repo = Repository(
- repoId = 42L,
- address = "http://example.org/",
- timestamp = 1337L,
- formatVersion = IndexFormatVersion.TWO,
- certificate = "abcd",
- version = 2001,
- weight = 0,
- lastUpdated = 23L,
- )
- val uri = defaultRepoUriBuilder.getUri(repo, SIGNED_FILE_NAME)
- assertEquals("http://example.org/$SIGNED_FILE_NAME", uri.toString())
- }
-
+ @Test
+ fun testDefaultUriBuilder() {
+ val repo =
+ Repository(
+ repoId = 42L,
+ address = "http://example.org/",
+ timestamp = 1337L,
+ formatVersion = IndexFormatVersion.TWO,
+ certificate = "abcd",
+ version = 2001,
+ weight = 0,
+ lastUpdated = 23L,
+ )
+ val uri = defaultRepoUriBuilder.getUri(repo, SIGNED_FILE_NAME)
+ assertEquals("http://example.org/$SIGNED_FILE_NAME", uri.toString())
+ }
}
diff --git a/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt
index b8706492d..bd6bc9be7 100644
--- a/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/index/v1/IndexV1UpdaterTest.kt
@@ -7,6 +7,13 @@ import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
import org.fdroid.CompatibilityChecker
import org.fdroid.database.DbTest
import org.fdroid.database.Repository
@@ -19,220 +26,209 @@ import org.fdroid.index.SigningException
import org.fdroid.index.TempFileProvider
import org.fdroid.index.v2.ANTI_FEATURE_KNOWN_VULNERABILITY
import org.fdroid.index.v2.FileV2
+import org.fdroid.test.TestUtils.getRes
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFalse
-import kotlin.test.assertIs
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class IndexV1UpdaterTest : DbTest() {
- @get:Rule
- var tmpFolder: TemporaryFolder = TemporaryFolder()
+ @get:Rule var tmpFolder: TemporaryFolder = TemporaryFolder()
- private val tempFileProvider: TempFileProvider = mockk()
- private val downloaderFactory: DownloaderFactory = mockk()
- private val downloader: Downloader = mockk()
- private val compatibilityChecker: CompatibilityChecker = CompatibilityChecker { true }
- private lateinit var indexUpdater: IndexV1Updater
+ private val tempFileProvider: TempFileProvider = mockk()
+ private val downloaderFactory: DownloaderFactory = mockk()
+ private val downloader: Downloader = mockk()
+ private val compatibilityChecker: CompatibilityChecker = CompatibilityChecker { true }
+ private lateinit var indexUpdater: IndexV1Updater
- @Before
- override fun createDb() {
- super.createDb()
- indexUpdater = IndexV1Updater(
- database = db,
- tempFileProvider = tempFileProvider,
- downloaderFactory = downloaderFactory,
- compatibilityChecker = compatibilityChecker,
- )
+ @Before
+ override fun createDb() {
+ super.createDb()
+ indexUpdater =
+ IndexV1Updater(
+ database = db,
+ tempFileProvider = tempFileProvider,
+ downloaderFactory = downloaderFactory,
+ compatibilityChecker = compatibilityChecker,
+ )
+ }
+
+ @Test
+ fun testIndexV1Processing() {
+ val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL, certificate = TESTY_CERT)
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ downloadIndex(repo, TESTY_JAR)
+ val result = indexUpdater.update(repo).noError()
+ assertIs(result)
+
+ // repo got updated
+ val updatedRepo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(TESTY_CERT, updatedRepo.certificate)
+ assertEquals(TESTY_FINGERPRINT, updatedRepo.fingerprint)
+
+ // some assertions ported from old IndexV1UpdaterTest
+ assertEquals(1, repoDao.getRepositories().size)
+ assertEquals(63, appDao.countApps())
+ listOf("fake.app.one", "org.adaway", "This_does_not_exist").forEach { packageName ->
+ assertNull(appDao.getApp(packageName).getOrAwaitValue())
}
-
- @Test
- fun testIndexV1Processing() {
- val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL, certificate = TESTY_CERT)
- val repo = repoDao.getRepository(repoId) ?: fail()
- downloadIndex(repo, TESTY_JAR)
- val result = indexUpdater.update(repo).noError()
- assertIs(result)
-
- // repo got updated
- val updatedRepo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(TESTY_CERT, updatedRepo.certificate)
- assertEquals(TESTY_FINGERPRINT, updatedRepo.fingerprint)
-
- // some assertions ported from old IndexV1UpdaterTest
- assertEquals(1, repoDao.getRepositories().size)
- assertEquals(63, appDao.countApps())
- listOf("fake.app.one", "org.adaway", "This_does_not_exist").forEach { packageName ->
- assertNull(appDao.getApp(packageName).getOrAwaitValue())
- }
- appDao.getAppMetadata().forEach { app ->
- val numVersions = versionDao.getVersions(listOf(app.packageName)).size
- assertTrue(numVersions > 0)
- }
- assertEquals(1497639511824, updatedRepo.timestamp)
- assertEquals(TESTY_CANONICAL_URL, updatedRepo.address)
- assertEquals("non-public test repo", updatedRepo.repository.name.values.first())
- assertEquals(18, updatedRepo.version)
- assertEquals("/icons/fdroid-icon.png", updatedRepo.repository.icon?.values?.first()?.name)
- val description = "This is a repository of apps to be used with F-Droid. " +
- "Applications in this repository are either official binaries built " +
- "by the original application developers, or are binaries built " +
- "from source by the admin of f-droid.org using the tools on " +
- "https://gitlab.com/u/fdroid. "
- assertEquals(description, updatedRepo.repository.description.values.first())
- assertEquals(
- setOf(TESTY_CANONICAL_URL, "http://frkcchxlcvnb4m5a.onion/fdroid/repo"),
- updatedRepo.mirrors.map { it.url }.toSet(),
- )
-
- // Make sure the per-apk anti features which are new in index v1 get added correctly.
- val wazeVersion = versionDao.getVersions(listOf("com.waze")).find {
- it.manifest.versionCode == 1019841L
- }
- assertNotNull(wazeVersion)
- assertEquals(setOf(ANTI_FEATURE_KNOWN_VULNERABILITY), wazeVersion.antiFeatures?.keys)
-
- val protoVersion = versionDao.getAppVersions("io.proto.player").getOrFail().find {
- it.version.versionCode == 1110L
- }
- assertNotNull(protoVersion)
- assertEquals("/io.proto.player-1.apk", protoVersion.version.file.name)
- val perms = protoVersion.usesPermission.map { it.name }
- assertTrue(perms.contains(Manifest.permission.READ_EXTERNAL_STORAGE))
- assertTrue(perms.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE))
- assertFalse(perms.contains(Manifest.permission.READ_CALENDAR))
- val icon = appDao.getApp("com.autonavi.minimap").getOrFail()?.icon?.values?.first()?.name
- assertEquals("/com.autonavi.minimap/en-US/icon.png", icon)
-
- // update again and get unchanged
- downloadIndex(updatedRepo, TESTY_JAR)
- val result2 = indexUpdater.update(updatedRepo).noError()
- assertIs(result2)
+ appDao.getAppMetadata().forEach { app ->
+ val numVersions = versionDao.getVersions(listOf(app.packageName)).size
+ assertTrue(numVersions > 0)
}
+ assertEquals(1497639511824, updatedRepo.timestamp)
+ assertEquals(TESTY_CANONICAL_URL, updatedRepo.address)
+ assertEquals("non-public test repo", updatedRepo.repository.name.values.first())
+ assertEquals(18, updatedRepo.version)
+ assertEquals("/icons/fdroid-icon.png", updatedRepo.repository.icon?.values?.first()?.name)
+ val description =
+ "This is a repository of apps to be used with F-Droid. " +
+ "Applications in this repository are either official binaries built " +
+ "by the original application developers, or are binaries built " +
+ "from source by the admin of f-droid.org using the tools on " +
+ "https://gitlab.com/u/fdroid. "
+ assertEquals(description, updatedRepo.repository.description.values.first())
+ assertEquals(
+ setOf(TESTY_CANONICAL_URL, "http://frkcchxlcvnb4m5a.onion/fdroid/repo"),
+ updatedRepo.mirrors.map { it.url }.toSet(),
+ )
- @Test
- fun testIndexV1WithWrongCert() {
- val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
- val repo = repoDao.getRepository(repoId) ?: fail()
- downloadIndex(repo, TESTY_JAR)
- val result = indexUpdater.update(repo)
- assertIs(result)
- assertIs(result.e)
+ // Make sure the per-apk anti features which are new in index v1 get added correctly.
+ val wazeVersion =
+ versionDao.getVersions(listOf("com.waze")).find { it.manifest.versionCode == 1019841L }
+ assertNotNull(wazeVersion)
+ assertEquals(setOf(ANTI_FEATURE_KNOWN_VULNERABILITY), wazeVersion.antiFeatures?.keys)
- // check that the DB transaction was rolled back and the DB wasn't changed
- // except for adding the error to the repo
- val expectedRepo = repo.copy(
- preferences = repo.preferences.copy(
- errorCount = 1,
- lastError = "Signing certificate does not match"
- ),
- )
- assertEquals(expectedRepo, repoDao.getRepository(repoId) ?: fail())
- assertEquals(0, appDao.countApps())
- assertEquals(0, versionDao.countAppVersions())
- }
+ val protoVersion =
+ versionDao.getAppVersions("io.proto.player").getOrFail().find {
+ it.version.versionCode == 1110L
+ }
+ assertNotNull(protoVersion)
+ assertEquals("/io.proto.player-1.apk", protoVersion.version.file.name)
+ val perms = protoVersion.usesPermission.map { it.name }
+ assertTrue(perms.contains(Manifest.permission.READ_EXTERNAL_STORAGE))
+ assertTrue(perms.contains(Manifest.permission.WRITE_EXTERNAL_STORAGE))
+ assertFalse(perms.contains(Manifest.permission.READ_CALENDAR))
+ val icon = appDao.getApp("com.autonavi.minimap").getOrFail()?.icon?.values?.first()?.name
+ assertEquals("/com.autonavi.minimap/en-US/icon.png", icon)
- @Test
- fun testIndexV1WithOldTimestamp() {
- val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
- val repo = repoDao.getRepository(repoId) ?: fail()
- val futureRepo =
- repo.copy(repository = repo.repository.copy(timestamp = System.currentTimeMillis()))
- downloadIndex(futureRepo, TESTY_JAR)
- val result = indexUpdater.update(futureRepo)
- assertIs(result)
- assertIs(result.e)
- assertFalse((result.e as OldIndexException).isSameTimestamp)
- }
+ // update again and get unchanged
+ downloadIndex(updatedRepo, TESTY_JAR)
+ val result2 = indexUpdater.update(updatedRepo).noError()
+ assertIs(result2)
+ }
- @Test
- fun testIndexV1WithCorruptAppPackageName() {
- val result = testBadTestyJar("testy.at.or.at_corrupt_app_package_name_index-v1.jar")
- assertIs(result)
- }
+ @Test
+ fun testIndexV1WithWrongCert() {
+ val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ downloadIndex(repo, TESTY_JAR)
+ val result = indexUpdater.update(repo)
+ assertIs(result)
+ assertIs(result.e)
- @Test
- fun testIndexV1WithCorruptPackageName() {
- val result = testBadTestyJar("testy.at.or.at_corrupt_package_name_index-v1.jar")
- assertIs(result)
- }
+ // check that the DB transaction was rolled back and the DB wasn't changed
+ // except for adding the error to the repo
+ val expectedRepo =
+ repo.copy(
+ preferences =
+ repo.preferences.copy(errorCount = 1, lastError = "Signing certificate does not match")
+ )
+ assertEquals(expectedRepo, repoDao.getRepository(repoId) ?: fail())
+ assertEquals(0, appDao.countApps())
+ assertEquals(0, versionDao.countAppVersions())
+ }
- @Test
- fun testIndexV1WithBadTestyJarNoManifest() {
- val result = testBadTestyJar("testy.at.or.at_no-MANIFEST.MF_index-v1.jar")
- assertIs(result)
- assertIs(result.e)
- }
+ @Test
+ fun testIndexV1WithOldTimestamp() {
+ val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ val futureRepo =
+ repo.copy(repository = repo.repository.copy(timestamp = System.currentTimeMillis()))
+ downloadIndex(futureRepo, TESTY_JAR)
+ val result = indexUpdater.update(futureRepo)
+ assertIs(result)
+ assertIs(result.e)
+ assertFalse((result.e as OldIndexException).isSameTimestamp)
+ }
- @Test
- fun testIndexV1WithBadTestyJarNoSigningCert() {
- val result = testBadTestyJar("testy.at.or.at_no-.RSA_index-v1.jar")
- assertIs(result)
- }
+ @Test
+ fun testIndexV1WithCorruptAppPackageName() {
+ val result = testBadTestyJar("testy.at.or.at_corrupt_app_package_name_index-v1.jar")
+ assertIs(result)
+ }
- @Test
- fun testIndexV1WithBadTestyJarNoSignature() {
- val result = testBadTestyJar("testy.at.or.at_no-.SF_index-v1.jar")
- assertIs(result)
- }
+ @Test
+ fun testIndexV1WithCorruptPackageName() {
+ val result = testBadTestyJar("testy.at.or.at_corrupt_package_name_index-v1.jar")
+ assertIs(result)
+ }
- @Test
- fun testIndexV1WithBadTestyJarNoSignatureFiles() {
- val result = testBadTestyJar("testy.at.or.at_no-signature_index-v1.jar")
- assertIs(result)
- assertIs(result.e)
- }
+ @Test
+ fun testIndexV1WithBadTestyJarNoManifest() {
+ val result = testBadTestyJar("testy.at.or.at_no-MANIFEST.MF_index-v1.jar")
+ assertIs(result)
+ assertIs(result.e)
+ }
- @Suppress("DEPRECATION")
- private fun downloadIndex(repo: Repository, jar: String) {
- val uri = Uri.parse("${repo.address}/$SIGNED_FILE_NAME")
- val indexFile = FileV2.fromPath("/$SIGNED_FILE_NAME")
+ @Test
+ fun testIndexV1WithBadTestyJarNoSigningCert() {
+ val result = testBadTestyJar("testy.at.or.at_no-.RSA_index-v1.jar")
+ assertIs(result)
+ }
- val jarFile = tmpFolder.newFile()
- assets.open(jar).use { inputStream ->
- jarFile.outputStream().use { inputStream.copyTo(it) }
- }
- every { tempFileProvider.createTempFile(null) } returns jarFile
- every {
- downloaderFactory.createWithTryFirstMirror(repo, uri, indexFile, jarFile)
- } returns downloader
- every { downloader.cacheTag = null } just Runs
- every { downloader.download() } just Runs
- every { downloader.hasChanged() } returns true
- every { downloader.cacheTag } returns null
- }
+ @Test
+ fun testIndexV1WithBadTestyJarNoSignature() {
+ val result = testBadTestyJar("testy.at.or.at_no-.SF_index-v1.jar")
+ assertIs(result)
+ }
- private fun testBadTestyJar(jar: String): IndexUpdateResult {
- val repoId = repoDao.insertEmptyRepo("http://example.org")
- val repo = repoDao.getRepository(repoId) ?: fail()
- downloadIndex(repo, jar)
- return indexUpdater.update(repo)
- }
+ @Test
+ fun testIndexV1WithBadTestyJarNoSignatureFiles() {
+ val result = testBadTestyJar("testy.at.or.at_no-signature_index-v1.jar")
+ assertIs(result)
+ assertIs(result.e)
+ }
- /**
- * Easier for debugging, if we throw the index error.
- */
- private fun IndexUpdateResult.noError(): IndexUpdateResult {
- if (this is IndexUpdateResult.Error) throw e
- return this
- }
+ @Suppress("DEPRECATION")
+ private fun downloadIndex(repo: Repository, jar: String) {
+ val uri = Uri.parse("${repo.address}/$SIGNED_FILE_NAME")
+ val indexFile = FileV2.fromPath("/$SIGNED_FILE_NAME")
+ val jarFile = tmpFolder.newFile()
+ getRes(jar).use { inputStream -> jarFile.outputStream().use { inputStream.copyTo(it) } }
+ every { tempFileProvider.createTempFile(null) } returns jarFile
+ every { downloaderFactory.createWithTryFirstMirror(repo, uri, indexFile, jarFile) } returns
+ downloader
+ every { downloader.cacheTag = null } just Runs
+ every { downloader.download() } just Runs
+ every { downloader.hasChanged() } returns true
+ every { downloader.cacheTag } returns null
+ }
+
+ private fun testBadTestyJar(jar: String): IndexUpdateResult {
+ val repoId = repoDao.insertEmptyRepo("http://example.org")
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ downloadIndex(repo, jar)
+ return indexUpdater.update(repo)
+ }
+
+ /** Easier for debugging, if we throw the index error. */
+ private fun IndexUpdateResult.noError(): IndexUpdateResult {
+ if (this is IndexUpdateResult.Error) throw e
+ return this
+ }
}
private const val TESTY_CANONICAL_URL = "http://testy.at.or.at/fdroid/repo"
private const val TESTY_JAR = "testy.at.or.at_index-v1.jar"
private const val TESTY_FINGERPRINT =
- "818e469465f96b704e27be2fee4c63ab9f83ddf30e7a34c7371a4728d83b0bc1"
-private const val TESTY_CERT = "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b" +
+ "818e469465f96b704e27be2fee4c63ab9f83ddf30e7a34c7371a4728d83b0bc1"
+private const val TESTY_CERT =
+ "308204e1308202c9a0030201020204483450fa300d06092a864886f70d01010b" +
"050030213110300e060355040b1307462d44726f6964310d300b060355040313" +
"04736f7661301e170d3136303832333133333131365a170d3434303130393133" +
"333131365a30213110300e060355040b1307462d44726f6964310d300b060355" +
diff --git a/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt b/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt
index ed8bcfea2..f20b518fd 100644
--- a/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt
+++ b/libs/database/src/dbTest/java/org/fdroid/index/v2/IndexV2UpdaterTest.kt
@@ -8,6 +8,11 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
+import java.util.concurrent.CountDownLatch
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNull
+import kotlin.test.fail
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
@@ -26,313 +31,318 @@ import org.fdroid.test.TestDataEntry
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
+import org.fdroid.test.TestUtils.getRes
import org.fdroid.test.VerifierConstants.CERTIFICATE
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
-import java.util.concurrent.CountDownLatch
-import kotlin.test.assertEquals
-import kotlin.test.assertIs
-import kotlin.test.assertNull
-import kotlin.test.fail
@RunWith(AndroidJUnit4::class)
internal class IndexV2UpdaterTest : DbTest() {
- @get:Rule
- var tmpFolder: TemporaryFolder = TemporaryFolder()
+ @get:Rule var tmpFolder: TemporaryFolder = TemporaryFolder()
- private val tempFileProvider: TempFileProvider = mockk()
- private val downloaderFactory: DownloaderFactory = mockk()
- private val downloader: Downloader = mockk()
- private val compatibilityChecker: CompatibilityChecker = CompatibilityChecker { true }
- private lateinit var indexUpdater: IndexV2Updater
+ private val tempFileProvider: TempFileProvider = mockk()
+ private val downloaderFactory: DownloaderFactory = mockk()
+ private val downloader: Downloader = mockk()
+ private val compatibilityChecker: CompatibilityChecker = CompatibilityChecker { true }
+ private lateinit var indexUpdater: IndexV2Updater
- @Before
- override fun createDb() {
- super.createDb()
- indexUpdater = IndexV2Updater(
- database = db,
- tempFileProvider = tempFileProvider,
- downloaderFactory = downloaderFactory,
- compatibilityChecker = compatibilityChecker,
- )
- }
+ @Before
+ override fun createDb() {
+ super.createDb()
+ indexUpdater =
+ IndexV2Updater(
+ database = db,
+ tempFileProvider = tempFileProvider,
+ downloaderFactory = downloaderFactory,
+ compatibilityChecker = compatibilityChecker,
+ )
+ }
- @Test
- fun testFullIndexEmptyToMin() {
- val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "index-min-v2.json",
- indexFileV2 = TestDataEntry.emptyToMin.index
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMinV2.index)
+ @Test
+ fun testFullIndexEmptyToMin() {
+ val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "index-min-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMin.index,
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMinV2.index)
- // check that format version got entered and certificate stayed the same
- val updatedRepo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(TWO, updatedRepo.formatVersion)
- assertEquals(CERTIFICATE, updatedRepo.certificate)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ // check that format version got entered and certificate stayed the same
+ val updatedRepo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(TWO, updatedRepo.formatVersion)
+ assertEquals(CERTIFICATE, updatedRepo.certificate)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testFullIndexEmptyToMid() {
- val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
- jsonPath = "index-mid-v2.json",
- indexFileV2 = TestDataEntry.emptyToMid.index
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMidV2.index)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testFullIndexEmptyToMid() {
+ val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
+ jsonPath = "index-mid-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMid.index,
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMidV2.index)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testFullIndexEmptyToMax() {
- val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-max/$SIGNED_FILE_NAME",
- jsonPath = "index-max-v2.json",
- indexFileV2 = TestDataEntry.emptyToMax.index
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMaxV2.index)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testFullIndexEmptyToMax() {
+ val repoId = repoDao.insertEmptyRepo("http://example.org", certificate = CERTIFICATE)
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-max/$SIGNED_FILE_NAME",
+ jsonPath = "index-max-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMax.index,
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMaxV2.index)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testDiffMinToMid() {
- val repoId = streamIndexV2IntoDb("index-min-v2.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-mid/42.json",
- indexFileV2 = TestDataEntry.emptyToMid.diffs["42"] ?: fail()
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMidV2.index)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testDiffMinToMid() {
+ val repoId = streamIndexV2IntoDb("index-min-v2.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-mid/42.json",
+ indexFileV2 = TestDataEntry.emptyToMid.diffs["42"] ?: fail(),
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMidV2.index)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testDiffEmptyToMin() {
- val repoId = streamIndexV2IntoDb("index-empty-v2.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-min/23.json",
- indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail()
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMinV2.index)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testDiffEmptyToMin() {
+ val repoId = streamIndexV2IntoDb("index-empty-v2.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-min/23.json",
+ indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail(),
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMinV2.index)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testDiffMidToMax() {
- val repoId = streamIndexV2IntoDb("index-mid-v2.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-max/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-max/1337.json",
- indexFileV2 = TestDataEntry.emptyToMax.diffs["1337"] ?: fail()
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMaxV2.index)
- assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testDiffMidToMax() {
+ val repoId = streamIndexV2IntoDb("index-mid-v2.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-max/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-max/1337.json",
+ indexFileV2 = TestDataEntry.emptyToMax.diffs["1337"] ?: fail(),
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMaxV2.index)
+ assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testSameTimestampUnchanged() {
- val repoId = streamIndexV2IntoDb("index-min-v2.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-min/23.json",
- indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail()
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Unchanged, result)
- assertDbEquals(repoId, TestDataMinV2.index)
- assertNull(repoDao.getRepository(repoId)?.lastUpdated)
- }
+ @Test
+ fun testSameTimestampUnchanged() {
+ val repoId = streamIndexV2IntoDb("index-min-v2.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-min/23.json",
+ indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail(),
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Unchanged, result)
+ assertDbEquals(repoId, TestDataMinV2.index)
+ assertNull(repoDao.getRepository(repoId)?.lastUpdated)
+ }
- @Test
- fun testHigherTimestampUnchanged() {
- val repoId = streamIndexV2IntoDb("index-mid-v2.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-min/23.json",
- indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail()
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Unchanged, result)
- assertDbEquals(repoId, TestDataMidV2.index)
- }
+ @Test
+ fun testHigherTimestampUnchanged() {
+ val repoId = streamIndexV2IntoDb("index-mid-v2.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-min/23.json",
+ indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail(),
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Unchanged, result)
+ assertDbEquals(repoId, TestDataMidV2.index)
+ }
- @Test
- fun testNoDiffFoundIndexFallback() {
- val repoId = streamIndexV2IntoDb("index-empty-v2.json")
- // fake timestamp of internal repo, so we will fail to find a diff in entry.json
- val newRepo = repoDao.getRepository(repoId)?.repository?.copy(timestamp = 22) ?: fail()
- repoDao.updateRepository(newRepo)
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "index-min-v2.json",
- indexFileV2 = TestDataEntry.emptyToMin.index
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMinV2.index)
- }
+ @Test
+ fun testNoDiffFoundIndexFallback() {
+ val repoId = streamIndexV2IntoDb("index-empty-v2.json")
+ // fake timestamp of internal repo, so we will fail to find a diff in entry.json
+ val newRepo = repoDao.getRepository(repoId)?.repository?.copy(timestamp = 22) ?: fail()
+ repoDao.updateRepository(newRepo)
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "index-min-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMin.index,
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMinV2.index)
+ }
- @Test
- fun testWrongFingerprint() {
- val repoId = repoDao.insertEmptyRepo("http://example.org")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "index-min-v2.json",
- indexFileV2 = TestDataEntry.emptyToMin.index
- )
- val result = indexUpdater.update(repo)
- assertIs(result)
- assertIs(result.e)
- }
+ @Test
+ fun testWrongFingerprint() {
+ val repoId = repoDao.insertEmptyRepo("http://example.org")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "index-min-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMin.index,
+ )
+ val result = indexUpdater.update(repo)
+ assertIs(result)
+ assertIs(result.e)
+ }
- /**
- * Ensures that a v1 repo can't use a diff when upgrading to v1,
- * but must use a full index update.
- */
- @Test
- fun testV1ToV2ForcesFullUpdateEvenIfDiffExists() {
- val repoId = streamIndexV1IntoDb("index-min-v1.json")
- val repo = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
- jsonPath = "index-mid-v2.json",
- indexFileV2 = TestDataEntry.emptyToMid.index,
- )
- val result = indexUpdater.update(repo).noError()
- assertEquals(IndexUpdateResult.Processed, result)
- assertDbEquals(repoId, TestDataMidV2.index)
+ /**
+ * Ensures that a v1 repo can't use a diff when upgrading to v1, but must use a full index update.
+ */
+ @Test
+ fun testV1ToV2ForcesFullUpdateEvenIfDiffExists() {
+ val repoId = streamIndexV1IntoDb("index-min-v1.json")
+ val repo =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-mid/$SIGNED_FILE_NAME",
+ jsonPath = "index-mid-v2.json",
+ indexFileV2 = TestDataEntry.emptyToMid.index,
+ )
+ val result = indexUpdater.update(repo).noError()
+ assertEquals(IndexUpdateResult.Processed, result)
+ assertDbEquals(repoId, TestDataMidV2.index)
- // check that format version got upgraded
- val updatedRepo = repoDao.getRepository(repoId) ?: fail()
- assertEquals(TWO, updatedRepo.formatVersion)
- }
+ // check that format version got upgraded
+ val updatedRepo = repoDao.getRepository(repoId) ?: fail()
+ assertEquals(TWO, updatedRepo.formatVersion)
+ }
- @Test
- @OptIn(DelicateCoroutinesApi::class)
- fun concurrentUpdateTest() {
- val db = spyk(db) // spy on the DB, so we can mock a call to aid in concurrency
- indexUpdater = IndexV2Updater(
- database = db,
- tempFileProvider = tempFileProvider,
- downloaderFactory = downloaderFactory,
- compatibilityChecker = compatibilityChecker,
- )
- val repoId = streamIndexV2IntoDb("index-empty-v2.json")
- val repo1 = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-min/23.json",
- indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail()
- )
- val repo2 = prepareUpdate(
- repoId = repoId,
- entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
- jsonPath = "diff-empty-min/23.json",
- indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail()
- )
- val latch = CountDownLatch(1)
- val runSlot = slot()
- every { db.runInTransaction(capture(runSlot)) } answers {
- runSlot.captured.run()
- latch.countDown()
- } andThenAnswer {
- latch.await()
- runSlot.captured.run()
- }
- runBlocking {
- GlobalScope.async {
- val result1 = indexUpdater.update(repo1).noError()
- assertEquals(IndexUpdateResult.Processed, result1)
+ @Test
+ @OptIn(DelicateCoroutinesApi::class)
+ fun concurrentUpdateTest() {
+ val db = spyk(db) // spy on the DB, so we can mock a call to aid in concurrency
+ indexUpdater =
+ IndexV2Updater(
+ database = db,
+ tempFileProvider = tempFileProvider,
+ downloaderFactory = downloaderFactory,
+ compatibilityChecker = compatibilityChecker,
+ )
+ val repoId = streamIndexV2IntoDb("index-empty-v2.json")
+ val repo1 =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-min/23.json",
+ indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail(),
+ )
+ val repo2 =
+ prepareUpdate(
+ repoId = repoId,
+ entryPath = "diff-empty-min/$SIGNED_FILE_NAME",
+ jsonPath = "diff-empty-min/23.json",
+ indexFileV2 = TestDataEntry.emptyToMin.diffs["23"] ?: fail(),
+ )
+ val latch = CountDownLatch(1)
+ val runSlot = slot()
+ every { db.runInTransaction(capture(runSlot)) } answers
+ {
+ runSlot.captured.run()
+ latch.countDown()
+ } andThenAnswer
+ {
+ latch.await()
+ runSlot.captured.run()
+ }
+ runBlocking {
+ GlobalScope.async {
+ val result1 = indexUpdater.update(repo1).noError()
+ assertEquals(IndexUpdateResult.Processed, result1)
- val entryFile = tmpFolder.newFile()
- val indexFile = tmpFolder.newFile()
- assets.open("diff-empty-min/$SIGNED_FILE_NAME").use { inputStream ->
- entryFile.outputStream().use { inputStream.copyTo(it) }
- }
- assets.open("diff-empty-min/23.json").use { inputStream ->
- indexFile.outputStream().use { inputStream.copyTo(it) }
- }
- every {
- tempFileProvider.createTempFile(any())
- } returnsMany listOf(entryFile, indexFile)
-
- val result2 = indexUpdater.update(repo2)
- assertIs(result2)
- assertIs(result2.e)
- }.await()
- }
- }
-
- private fun prepareUpdate(
- repoId: Long,
- entryPath: String,
- jsonPath: String,
- indexFileV2: EntryFileV2,
- ): Repository {
- val entryFileV2 = FileV2.fromPath("/$SIGNED_FILE_NAME")
- val entryFile = tmpFolder.newFile()
- val indexFile = tmpFolder.newFile()
- val repo = repoDao.getRepository(repoId) ?: fail()
- val entryUri = Uri.parse("${repo.address}/$SIGNED_FILE_NAME")
- val indexUri = Uri.parse("${repo.address}/${indexFileV2.name.trimStart('/')}")
-
- assets.open(entryPath).use { inputStream ->
+ val entryFile = tmpFolder.newFile()
+ val indexFile = tmpFolder.newFile()
+ getRes("diff-empty-min/$SIGNED_FILE_NAME").use { inputStream ->
entryFile.outputStream().use { inputStream.copyTo(it) }
- }
- assets.open(jsonPath).use { inputStream ->
+ }
+ getRes("diff-empty-min/23.json").use { inputStream ->
indexFile.outputStream().use { inputStream.copyTo(it) }
+ }
+ every { tempFileProvider.createTempFile(any()) } returnsMany listOf(entryFile, indexFile)
+
+ val result2 = indexUpdater.update(repo2)
+ assertIs(result2)
+ assertIs(result2.e)
}
+ .await()
+ }
+ }
- every { tempFileProvider.createTempFile(any()) } returnsMany listOf(entryFile, indexFile)
- every {
- downloaderFactory.createWithTryFirstMirror(repo, entryUri, entryFileV2, any())
- } returns downloader
- every { downloader.download() } just Runs
- every {
- downloaderFactory.createWithTryFirstMirror(repo, indexUri, indexFileV2, any())
- } returns downloader
- every { downloader.download() } just Runs
+ private fun prepareUpdate(
+ repoId: Long,
+ entryPath: String,
+ jsonPath: String,
+ indexFileV2: EntryFileV2,
+ ): Repository {
+ val entryFileV2 = FileV2.fromPath("/$SIGNED_FILE_NAME")
+ val entryFile = tmpFolder.newFile()
+ val indexFile = tmpFolder.newFile()
+ val repo = repoDao.getRepository(repoId) ?: fail()
+ val entryUri = Uri.parse("${repo.address}/$SIGNED_FILE_NAME")
+ val indexUri = Uri.parse("${repo.address}/${indexFileV2.name.trimStart('/')}")
- return repo
+ getRes(entryPath).use { inputStream ->
+ entryFile.outputStream().use { inputStream.copyTo(it) }
+ }
+ getRes(jsonPath).use { inputStream ->
+ indexFile.outputStream().use { inputStream.copyTo(it) }
}
- /**
- * Easier for debugging, if we throw the index error.
- */
- private fun IndexUpdateResult.noError(): IndexUpdateResult {
- if (this is IndexUpdateResult.Error) throw e
- return this
- }
+ every { tempFileProvider.createTempFile(any()) } returnsMany listOf(entryFile, indexFile)
+ every { downloaderFactory.createWithTryFirstMirror(repo, entryUri, entryFileV2, any()) } returns
+ downloader
+ every { downloader.download() } just Runs
+ every { downloaderFactory.createWithTryFirstMirror(repo, indexUri, indexFileV2, any()) } returns
+ downloader
+ every { downloader.download() } just Runs
+ return repo
+ }
+
+ /** Easier for debugging, if we throw the index error. */
+ private fun IndexUpdateResult.noError(): IndexUpdateResult {
+ if (this is IndexUpdateResult.Error) throw e
+ return this
+ }
}
diff --git a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt
index 0f4b2c1cd..db05d980b 100644
--- a/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt
+++ b/libs/database/src/main/java/org/fdroid/repo/RepoAdder.kt
@@ -37,8 +37,8 @@ import org.fdroid.database.Repository
import org.fdroid.database.RepositoryDaoInt
import org.fdroid.download.DownloaderFactory
import org.fdroid.download.HttpManager
-import org.fdroid.download.HttpManager.Companion.isInvalidHttpUrl
import org.fdroid.download.NotFoundException
+import org.fdroid.download.isInvalidHttpUrl
import org.fdroid.index.IndexFormatVersion
import org.fdroid.index.IndexUpdateResult
import org.fdroid.index.RepoUpdater
diff --git a/libs/database/src/test/java/org/fdroid/database/ConvertersTest.kt b/libs/database/src/test/java/org/fdroid/database/ConvertersTest.kt
index c1cc7abf6..bc8ad4fa1 100644
--- a/libs/database/src/test/java/org/fdroid/database/ConvertersTest.kt
+++ b/libs/database/src/test/java/org/fdroid/database/ConvertersTest.kt
@@ -1,11 +1,11 @@
package org.fdroid.database
-import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
import org.fdroid.test.TestRepoUtils.getRandomLocalizedFileV2
import org.fdroid.test.TestUtils.getRandomList
import org.fdroid.test.TestUtils.getRandomString
+import org.junit.Test
internal class ConvertersTest {
diff --git a/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt b/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt
index b04dc3c78..b4a94ab09 100644
--- a/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt
+++ b/libs/database/src/test/java/org/fdroid/repo/RepoAdderTest.kt
@@ -1,7 +1,6 @@
package org.fdroid.repo
import android.content.Context
-import android.content.res.AssetManager
import android.net.Uri
import android.os.UserManager
import android.os.UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES
@@ -68,6 +67,7 @@ import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
import org.fdroid.test.TestUtils.decodeHex
import org.fdroid.test.TestUtils.getRandomString
+import org.fdroid.test.TestUtils.getRes
import org.fdroid.test.VerifierConstants
import org.junit.Rule
import org.junit.Test
@@ -90,7 +90,6 @@ internal class RepoAdderTest {
private val digest = mockk()
private val repoAdder: RepoAdder
- private val assets: AssetManager = context.resources.assets
private val localeList = LocaleListCompat.getDefault()
init {
@@ -486,9 +485,7 @@ internal class RepoAdderTest {
every { downloader.download() } answers
{
jarFile.outputStream().use { outputStream ->
- assets.open("diff-empty-min/entry.jar").use { inputStream ->
- inputStream.copyTo(outputStream)
- }
+ getRes("diff-empty-min/entry.jar").use { inputStream -> inputStream.copyTo(outputStream) }
}
}
coEvery {
@@ -601,9 +598,7 @@ internal class RepoAdderTest {
every { downloader.download() } answers
{
jarFile.outputStream().use { outputStream ->
- assets.open("diff-empty-min/entry.jar").use { inputStream ->
- inputStream.copyTo(outputStream)
- }
+ getRes("diff-empty-min/entry.jar").use { inputStream -> inputStream.copyTo(outputStream) }
}
}
coEvery {
@@ -662,7 +657,7 @@ internal class RepoAdderTest {
every { downloader.download() } answers
{
jarFile.outputStream().use { outputStream ->
- assets.open("guardianproject_entry.jar").use { inputStream ->
+ getRes("guardianproject_entry.jar").use { inputStream ->
inputStream.copyTo(outputStream)
}
}
@@ -737,7 +732,7 @@ internal class RepoAdderTest {
every { downloaderV1.download() } answers
{
jarFile.outputStream().use { outputStream ->
- assets.open("testy.at.or.at_index-v1.jar").use { inputStream ->
+ getRes("testy.at.or.at_index-v1.jar").use { inputStream ->
inputStream.copyTo(outputStream)
}
}
@@ -898,7 +893,7 @@ internal class RepoAdderTest {
val jarFile = folder.newFile()
- val indexInputStream = assets.open(indexFile)
+ val indexInputStream = getRes(indexFile)
val indexDigestStream = DigestInputStream(indexInputStream, digest)
every { tempFileProvider.createTempFile(any()) } returns jarFile
@@ -914,7 +909,7 @@ internal class RepoAdderTest {
every { downloader.download() } answers
{
jarFile.outputStream().use { outputStream ->
- assets.open(entryJar).use { inputStream -> inputStream.copyTo(outputStream) }
+ getRes(entryJar).use { inputStream -> inputStream.copyTo(outputStream) }
}
}
coEvery {
diff --git a/libs/download/api/jvm/download.api b/libs/download/api/jvm/download.api
new file mode 100644
index 000000000..3460c4c11
--- /dev/null
+++ b/libs/download/api/jvm/download.api
@@ -0,0 +1,137 @@
+public abstract interface class org/fdroid/download/BytesReceiver {
+ public abstract fun receive ([BLjava/lang/Long;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+}
+
+public abstract interface class org/fdroid/download/CustomDns {
+}
+
+public final class org/fdroid/download/DownloadRequest {
+ public fun (Ljava/lang/String;Ljava/util/List;)V
+ public fun (Ljava/lang/String;Ljava/util/List;Ljava/net/Proxy;)V
+ public fun (Ljava/lang/String;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;)V
+ public synthetic fun (Ljava/lang/String;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public fun (Lorg/fdroid/IndexFile;Ljava/util/List;)V
+ public fun (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;)V
+ public fun (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;)V
+ public fun (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;)V
+ public fun (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;)V
+ public synthetic fun (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lorg/fdroid/IndexFile;
+ public final fun component2 ()Ljava/util/List;
+ public final fun component3 ()Ljava/net/Proxy;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Lorg/fdroid/download/Mirror;
+ public final fun copy (Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;)Lorg/fdroid/download/DownloadRequest;
+ public static synthetic fun copy$default (Lorg/fdroid/download/DownloadRequest;Lorg/fdroid/IndexFile;Ljava/util/List;Ljava/net/Proxy;Ljava/lang/String;Ljava/lang/String;Lorg/fdroid/download/Mirror;ILjava/lang/Object;)Lorg/fdroid/download/DownloadRequest;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getHasCredentials ()Z
+ public final fun getIndexFile ()Lorg/fdroid/IndexFile;
+ public final fun getMirrors ()Ljava/util/List;
+ public final fun getPassword ()Ljava/lang/String;
+ public final fun getProxy ()Ljava/net/Proxy;
+ public final fun getTryFirstMirror ()Lorg/fdroid/download/Mirror;
+ public final fun getUsername ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/fdroid/download/HeadInfo {
+ public fun (ZLjava/lang/String;Ljava/lang/Long;Ljava/lang/String;)V
+ public final fun component1 ()Z
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Long;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun copy (ZLjava/lang/String;Ljava/lang/Long;Ljava/lang/String;)Lorg/fdroid/download/HeadInfo;
+ public static synthetic fun copy$default (Lorg/fdroid/download/HeadInfo;ZLjava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/download/HeadInfo;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getContentLength ()Ljava/lang/Long;
+ public final fun getETag ()Ljava/lang/String;
+ public final fun getETagChanged ()Z
+ public final fun getLastModified ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public class org/fdroid/download/HttpManager {
+ public static final field Companion Lorg/fdroid/download/HttpManager$Companion;
+ public fun (Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;Lorg/fdroid/download/MirrorParameterManager;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;Lorg/fdroid/download/MirrorParameterManager;Z)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;Lorg/fdroid/download/MirrorParameterManager;ZLorg/fdroid/download/MirrorChooser;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;Lorg/fdroid/download/MirrorParameterManager;ZLorg/fdroid/download/MirrorChooser;Lio/ktor/client/engine/HttpClientEngineFactory;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lorg/fdroid/download/CustomDns;Lorg/fdroid/download/MirrorParameterManager;ZLorg/fdroid/download/MirrorChooser;Lio/ktor/client/engine/HttpClientEngineFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun get (Lorg/fdroid/download/DownloadRequest;Ljava/lang/Long;Lorg/fdroid/download/BytesReceiver;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public final fun get (Lorg/fdroid/download/DownloadRequest;Lorg/fdroid/download/BytesReceiver;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun get$default (Lorg/fdroid/download/HttpManager;Lorg/fdroid/download/DownloadRequest;Ljava/lang/Long;Lorg/fdroid/download/BytesReceiver;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public final fun head (Lorg/fdroid/download/DownloadRequest;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun head$default (Lorg/fdroid/download/HttpManager;Lorg/fdroid/download/DownloadRequest;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+ public final fun post (Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static synthetic fun post$default (Lorg/fdroid/download/HttpManager;Ljava/lang/String;Ljava/lang/String;Ljava/net/Proxy;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
+}
+
+public final class org/fdroid/download/HttpManager$Companion {
+}
+
+public final class org/fdroid/download/Mirror {
+ public static final field Companion Lorg/fdroid/download/Mirror$Companion;
+ public fun (Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;)V
+ public fun (Ljava/lang/String;Ljava/lang/String;Z)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Z
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Z)Lorg/fdroid/download/Mirror;
+ public static synthetic fun copy$default (Lorg/fdroid/download/Mirror;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lorg/fdroid/download/Mirror;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun fromStrings (Ljava/util/List;)Ljava/util/List;
+ public final fun getBaseUrl ()Ljava/lang/String;
+ public final fun getCountryCode ()Ljava/lang/String;
+ public final fun getFDroidLinkUrl (Ljava/lang/String;)Ljava/lang/String;
+ public final fun getUrl ()Lio/ktor/http/Url;
+ public final fun getUrl (Ljava/lang/String;)Lio/ktor/http/Url;
+ public fun hashCode ()I
+ public final fun isHttp ()Z
+ public final fun isIpfsGateway ()Z
+ public final fun isLocal ()Z
+ public final fun isOnion ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/fdroid/download/Mirror$Companion {
+ public final fun fromStrings (Ljava/util/List;)Ljava/util/List;
+}
+
+public abstract interface class org/fdroid/download/MirrorChooser {
+ public abstract fun mirrorRequest (Lorg/fdroid/download/DownloadRequest;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public abstract fun orderMirrors (Lorg/fdroid/download/DownloadRequest;)Ljava/util/List;
+}
+
+public abstract interface class org/fdroid/download/MirrorParameterManager {
+ public abstract fun getCurrentLocation ()Ljava/lang/String;
+ public abstract fun getMirrorErrorCount (Ljava/lang/String;)I
+ public abstract fun incrementMirrorErrorCount (Ljava/lang/String;)V
+ public abstract fun preferForeignMirrors ()Z
+ public abstract fun shouldRetryRequest (Ljava/lang/String;)Z
+}
+
+public final class org/fdroid/download/NoResumeException : java/lang/Exception {
+ public fun ()V
+}
+
+public final class org/fdroid/download/NotFoundException : java/lang/Exception {
+ public fun ()V
+ public fun (Ljava/lang/Throwable;)V
+ public synthetic fun (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+}
+
+public abstract interface class org/fdroid/fdroid/ProgressListener {
+ public abstract fun onProgress (JJ)V
+}
+
diff --git a/libs/download/build.gradle.kts b/libs/download/build.gradle.kts
index cceebda02..227e2ce27 100644
--- a/libs/download/build.gradle.kts
+++ b/libs/download/build.gradle.kts
@@ -11,12 +11,13 @@ kotlin {
explicitApi()
@OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
abiValidation { enabled = true }
+
+ jvm()
android {
namespace = "org.fdroid.download"
compileSdk = libs.versions.compileSdk.get().toInt()
minSdk = 21
- withJava()
compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 }
withHostTest { packaging { resources.excludes.add("META-INF/*") } }
@@ -24,7 +25,7 @@ kotlin {
instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
instrumentationRunnerArguments["disableAnalytics"] = "true"
}
-
+ androidResources { enable = true }
lint {
checkReleaseBuilds = false
abortOnError = true
@@ -51,7 +52,6 @@ kotlin {
implementation(libs.mockk)
}
}
- // JVM is disabled for now, because Android app is including it instead of Android library
jvmMain { dependencies { implementation(libs.ktor.client.cio) } }
jvmTest { dependencies { implementation(libs.junit) } }
androidMain {
diff --git a/libs/download/src/androidMain/kotlin/org/fdroid/download/HttpManager.kt b/libs/download/src/androidMain/kotlin/org/fdroid/download/HttpManager.kt
index 1ea2f17fa..e10be9d3a 100644
--- a/libs/download/src/androidMain/kotlin/org/fdroid/download/HttpManager.kt
+++ b/libs/download/src/androidMain/kotlin/org/fdroid/download/HttpManager.kt
@@ -14,6 +14,7 @@ import okhttp3.ConnectionSpec.Companion.CLEARTEXT
import okhttp3.ConnectionSpec.Companion.MODERN_TLS
import okhttp3.ConnectionSpec.Companion.RESTRICTED_TLS
import okhttp3.Dns
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.internal.tls.OkHostnameVerifier
internal actual fun getHttpClientEngineFactory(customDns: Dns?): HttpClientEngineFactory<*> {
@@ -51,6 +52,10 @@ internal actual fun getHttpClientEngineFactory(customDns: Dns?): HttpClientEngin
}
}
+public fun isInvalidHttpUrl(url: String): Boolean {
+ return url.toHttpUrlOrNull() == null
+}
+
public suspend fun HttpManager.getInputStream(request: DownloadRequest): InputStream {
return getChannel(request).toInputStream()
}
diff --git a/libs/download/src/commonMain/kotlin/org/fdroid/download/HttpManager.kt b/libs/download/src/commonMain/kotlin/org/fdroid/download/HttpManager.kt
index bc0274fc6..392ab8ca4 100644
--- a/libs/download/src/commonMain/kotlin/org/fdroid/download/HttpManager.kt
+++ b/libs/download/src/commonMain/kotlin/org/fdroid/download/HttpManager.kt
@@ -60,8 +60,6 @@ constructor(
internal val log = KotlinLogging.logger {}
internal const val READ_BUFFER = 8 * 1024
private const val TIMEOUT_MILLIS_HIGH = 300_000L
-
- public fun isInvalidHttpUrl(url: String): Boolean = url.toHttpUrlOrNull() == null
}
private var httpClient = getNewHttpClient(proxyConfig)
diff --git a/libs/index/api/jvm/index.api b/libs/index/api/jvm/index.api
new file mode 100644
index 000000000..09ead77a6
--- /dev/null
+++ b/libs/index/api/jvm/index.api
@@ -0,0 +1,1119 @@
+public final class org/fdroid/index/IndexConverter {
+ public fun ()V
+ public fun (Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun toIndexV2 (Lorg/fdroid/index/v1/IndexV1;)Lorg/fdroid/index/v2/IndexV2;
+}
+
+public final class org/fdroid/index/IndexConverterKt {
+ public static final field RELEASE_CHANNEL_BETA Ljava/lang/String;
+ public static final fun getV1ReleaseChannels ()Ljava/util/Map;
+}
+
+public final class org/fdroid/index/IndexParser {
+ public static final field INSTANCE Lorg/fdroid/index/IndexParser;
+ public static final fun getJson ()Lkotlinx/serialization/json/Json;
+ public static final fun parseEntry (Ljava/lang/String;)Lorg/fdroid/index/v2/Entry;
+ public static final fun parseV1 (Ljava/lang/String;)Lorg/fdroid/index/v1/IndexV1;
+ public static final fun parseV2 (Ljava/lang/String;)Lorg/fdroid/index/v2/IndexV2;
+}
+
+public final class org/fdroid/index/v1/AppV1 {
+ public static final field Companion Lorg/fdroid/index/v1/AppV1$Companion;
+ public fun (Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/util/List;)V
+ public synthetic fun (Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/List;
+ public final fun component10 ()Ljava/lang/String;
+ public final fun component11 ()Ljava/lang/String;
+ public final fun component12 ()Ljava/lang/String;
+ public final fun component13 ()Ljava/lang/String;
+ public final fun component14 ()Ljava/lang/String;
+ public final fun component15 ()Ljava/lang/String;
+ public final fun component16 ()Ljava/lang/String;
+ public final fun component17 ()Ljava/lang/String;
+ public final fun component18 ()Ljava/lang/String;
+ public final fun component19 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/util/List;
+ public final fun component20 ()Ljava/lang/String;
+ public final fun component21 ()Ljava/lang/String;
+ public final fun component22 ()Ljava/lang/String;
+ public final fun component23 ()Ljava/lang/String;
+ public final fun component24 ()Ljava/lang/String;
+ public final fun component25 ()Ljava/lang/String;
+ public final fun component26 ()Ljava/lang/Long;
+ public final fun component27 ()Ljava/lang/String;
+ public final fun component28 ()Ljava/lang/String;
+ public final fun component29 ()Ljava/lang/Long;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component30 ()Ljava/util/Map;
+ public final fun component31 ()Ljava/util/List;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/lang/String;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun component8 ()Ljava/lang/String;
+ public final fun component9 ()Ljava/lang/String;
+ public final fun copy (Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/util/List;)Lorg/fdroid/index/v1/AppV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/AppV1;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/util/Map;Ljava/util/List;ILjava/lang/Object;)Lorg/fdroid/index/v1/AppV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAdded ()Ljava/lang/Long;
+ public final fun getAllowedAPKSigningKeys ()Ljava/util/List;
+ public final fun getAntiFeatures ()Ljava/util/List;
+ public final fun getAuthorEmail ()Ljava/lang/String;
+ public final fun getAuthorName ()Ljava/lang/String;
+ public final fun getAuthorPhone ()Ljava/lang/String;
+ public final fun getAuthorWebSite ()Ljava/lang/String;
+ public final fun getBinaries ()Ljava/lang/String;
+ public final fun getBitcoin ()Ljava/lang/String;
+ public final fun getCategories ()Ljava/util/List;
+ public final fun getChangelog ()Ljava/lang/String;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getDonate ()Ljava/lang/String;
+ public final fun getFlattrID ()Ljava/lang/String;
+ public final fun getIcon ()Ljava/lang/String;
+ public final fun getIssueTracker ()Ljava/lang/String;
+ public final fun getLastUpdated ()Ljava/lang/Long;
+ public final fun getLiberapay ()Ljava/lang/String;
+ public final fun getLiberapayID ()Ljava/lang/String;
+ public final fun getLicense ()Ljava/lang/String;
+ public final fun getLitecoin ()Ljava/lang/String;
+ public final fun getLocalized ()Ljava/util/Map;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getOpenCollective ()Ljava/lang/String;
+ public final fun getPackageName ()Ljava/lang/String;
+ public final fun getSourceCode ()Ljava/lang/String;
+ public final fun getSuggestedVersionCode ()Ljava/lang/String;
+ public final fun getSuggestedVersionName ()Ljava/lang/String;
+ public final fun getSummary ()Ljava/lang/String;
+ public final fun getTranslation ()Ljava/lang/String;
+ public final fun getWebSite ()Ljava/lang/String;
+ public fun hashCode ()I
+ public final fun toMetadataV2 (Ljava/lang/String;Ljava/lang/String;)Lorg/fdroid/index/v2/MetadataV2;
+ public static synthetic fun toMetadataV2$default (Lorg/fdroid/index/v1/AppV1;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v2/MetadataV2;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/AppV1$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/AppV1$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/AppV1;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/AppV1;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/AppV1$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/IndexV1 {
+ public static final field Companion Lorg/fdroid/index/v1/IndexV1$Companion;
+ public fun (Lorg/fdroid/index/v1/RepoV1;Lorg/fdroid/index/v1/Requests;Ljava/util/List;Ljava/util/Map;)V
+ public synthetic fun (Lorg/fdroid/index/v1/RepoV1;Lorg/fdroid/index/v1/Requests;Ljava/util/List;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lorg/fdroid/index/v1/RepoV1;
+ public final fun component2 ()Lorg/fdroid/index/v1/Requests;
+ public final fun component3 ()Ljava/util/List;
+ public final fun component4 ()Ljava/util/Map;
+ public final fun copy (Lorg/fdroid/index/v1/RepoV1;Lorg/fdroid/index/v1/Requests;Ljava/util/List;Ljava/util/Map;)Lorg/fdroid/index/v1/IndexV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/IndexV1;Lorg/fdroid/index/v1/RepoV1;Lorg/fdroid/index/v1/Requests;Ljava/util/List;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v1/IndexV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getApps ()Ljava/util/List;
+ public final fun getPackages ()Ljava/util/Map;
+ public final fun getRepo ()Lorg/fdroid/index/v1/RepoV1;
+ public final fun getRequests ()Lorg/fdroid/index/v1/Requests;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/IndexV1$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/IndexV1$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/IndexV1;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/IndexV1;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/IndexV1$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class org/fdroid/index/v1/IndexV1StreamReceiver {
+ public abstract fun receive (Ljava/lang/String;Ljava/util/Map;)V
+ public abstract fun receive (Ljava/lang/String;Lorg/fdroid/index/v2/MetadataV2;)V
+ public abstract fun receive (Lorg/fdroid/index/v2/RepoV2;J)V
+ public abstract fun updateAppMetadata (Ljava/lang/String;Ljava/lang/String;)V
+ public abstract fun updateRepo (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+}
+
+public final class org/fdroid/index/v1/Localized {
+ public static final field Companion Lorg/fdroid/index/v1/Localized$Companion;
+ public fun ()V
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component10 ()Ljava/util/List;
+ public final fun component11 ()Ljava/lang/String;
+ public final fun component12 ()Ljava/lang/String;
+ public final fun component13 ()Ljava/lang/String;
+ public final fun component14 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/util/List;
+ public final fun component7 ()Ljava/util/List;
+ public final fun component8 ()Ljava/util/List;
+ public final fun component9 ()Ljava/util/List;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lorg/fdroid/index/v1/Localized;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/Localized;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v1/Localized;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getFeatureGraphic ()Ljava/lang/String;
+ public final fun getIcon ()Ljava/lang/String;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getPhoneScreenshots ()Ljava/util/List;
+ public final fun getPromoGraphic ()Ljava/lang/String;
+ public final fun getSevenInchScreenshots ()Ljava/util/List;
+ public final fun getSummary ()Ljava/lang/String;
+ public final fun getTenInchScreenshots ()Ljava/util/List;
+ public final fun getTvBanner ()Ljava/lang/String;
+ public final fun getTvScreenshots ()Ljava/util/List;
+ public final fun getVideo ()Ljava/lang/String;
+ public final fun getWearScreenshots ()Ljava/util/List;
+ public final fun getWhatsNew ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/Localized$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/Localized$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/Localized;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/Localized;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/Localized$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/PackageV1 {
+ public static final field Companion Lorg/fdroid/index/v1/PackageV1$Companion;
+ public fun (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
+ public synthetic fun (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/Long;
+ public final fun component10 ()Ljava/lang/String;
+ public final fun component11 ()J
+ public final fun component12 ()Ljava/lang/String;
+ public final fun component13 ()Ljava/util/List;
+ public final fun component14 ()Ljava/util/List;
+ public final fun component15 ()Ljava/lang/Long;
+ public final fun component16 ()Ljava/lang/String;
+ public final fun component17 ()Ljava/util/List;
+ public final fun component18 ()Ljava/util/List;
+ public final fun component19 ()Ljava/util/List;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/Integer;
+ public final fun component6 ()Ljava/lang/Integer;
+ public final fun component7 ()Ljava/lang/Integer;
+ public final fun component8 ()Ljava/lang/String;
+ public final fun component9 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/fdroid/index/v1/PackageV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/PackageV1;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/lang/Long;Ljava/lang/String;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/fdroid/index/v1/PackageV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAdded ()Ljava/lang/Long;
+ public final fun getAntiFeatures ()Ljava/util/List;
+ public final fun getApkName ()Ljava/lang/String;
+ public final fun getFeatures ()Ljava/util/List;
+ public final fun getHash ()Ljava/lang/String;
+ public final fun getHashType ()Ljava/lang/String;
+ public final fun getMaxSdkVersion ()Ljava/lang/Integer;
+ public final fun getMinSdkVersion ()Ljava/lang/Integer;
+ public final fun getNativeCode ()Ljava/util/List;
+ public final fun getPackageName ()Ljava/lang/String;
+ public final fun getSig ()Ljava/lang/String;
+ public final fun getSigner ()Ljava/lang/String;
+ public final fun getSize ()J
+ public final fun getSrcName ()Ljava/lang/String;
+ public final fun getTargetSdkVersion ()Ljava/lang/Integer;
+ public final fun getUsesPermission ()Ljava/util/List;
+ public final fun getUsesPermission23 ()Ljava/util/List;
+ public final fun getVersionCode ()Ljava/lang/Long;
+ public final fun getVersionName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public final fun toPackageVersionV2 (Ljava/util/List;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/PackageVersionV2;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/PackageV1$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/PackageV1$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/PackageV1;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/PackageV1;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/PackageV1$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/PermissionV1 {
+ public static final field Companion Lorg/fdroid/index/v1/PermissionV1$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/Integer;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/Integer;
+ public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lorg/fdroid/index/v1/PermissionV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/PermissionV1;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lorg/fdroid/index/v1/PermissionV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMaxSdk ()Ljava/lang/Integer;
+ public final fun getName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final class org/fdroid/index/v1/PermissionV1$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/RepoV1 {
+ public static final field Companion Lorg/fdroid/index/v1/RepoV1$Companion;
+ public fun (JILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V
+ public synthetic fun (JILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()J
+ public final fun component2 ()I
+ public final fun component3 ()Ljava/lang/Integer;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/lang/String;
+ public final fun component6 ()Ljava/lang/String;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun component8 ()Ljava/util/List;
+ public final fun copy (JILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lorg/fdroid/index/v1/RepoV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/RepoV1;JILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lorg/fdroid/index/v1/RepoV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAddress ()Ljava/lang/String;
+ public final fun getDescription ()Ljava/lang/String;
+ public final fun getIcon ()Ljava/lang/String;
+ public final fun getMaxAge ()Ljava/lang/Integer;
+ public final fun getMirrors ()Ljava/util/List;
+ public final fun getName ()Ljava/lang/String;
+ public final fun getTimestamp ()J
+ public final fun getVersion ()I
+ public fun hashCode ()I
+ public final fun toRepoV2 (Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/RepoV2;
+ public static synthetic fun toRepoV2$default (Lorg/fdroid/index/v1/RepoV1;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/RepoV2;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/RepoV1$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/RepoV1$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/RepoV1;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/RepoV1;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/RepoV1$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/Requests {
+ public static final field Companion Lorg/fdroid/index/v1/Requests$Companion;
+ public fun (Ljava/util/List;Ljava/util/List;)V
+ public final fun component1 ()Ljava/util/List;
+ public final fun component2 ()Ljava/util/List;
+ public final fun copy (Ljava/util/List;Ljava/util/List;)Lorg/fdroid/index/v1/Requests;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v1/Requests;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/fdroid/index/v1/Requests;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getInstall ()Ljava/util/List;
+ public final fun getUninstall ()Ljava/util/List;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v1/Requests$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v1/Requests$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v1/Requests;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v1/Requests;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v1/Requests$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/AntiFeatureV2 {
+ public static final field Companion Lorg/fdroid/index/v2/AntiFeatureV2$Companion;
+ public fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/util/Map;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/AntiFeatureV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/AntiFeatureV2;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/AntiFeatureV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDescription ()Ljava/util/Map;
+ public final fun getIcon ()Ljava/util/Map;
+ public final fun getName ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/AntiFeatureV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/AntiFeatureV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/AntiFeatureV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/AntiFeatureV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/AntiFeatureV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/CategoryV2 {
+ public static final field Companion Lorg/fdroid/index/v2/CategoryV2$Companion;
+ public fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/util/Map;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/CategoryV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/CategoryV2;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/CategoryV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDescription ()Ljava/util/Map;
+ public final fun getIcon ()Ljava/util/Map;
+ public final fun getName ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/CategoryV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/CategoryV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/CategoryV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/CategoryV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/CategoryV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/Entry {
+ public static final field Companion Lorg/fdroid/index/v2/Entry$Companion;
+ public fun (JJLjava/lang/Integer;Lorg/fdroid/index/v2/EntryFileV2;Ljava/util/Map;)V
+ public synthetic fun (JJLjava/lang/Integer;Lorg/fdroid/index/v2/EntryFileV2;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()J
+ public final fun component2 ()J
+ public final fun component3 ()Ljava/lang/Integer;
+ public final fun component4 ()Lorg/fdroid/index/v2/EntryFileV2;
+ public final fun component5 ()Ljava/util/Map;
+ public final fun copy (JJLjava/lang/Integer;Lorg/fdroid/index/v2/EntryFileV2;Ljava/util/Map;)Lorg/fdroid/index/v2/Entry;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/Entry;JJLjava/lang/Integer;Lorg/fdroid/index/v2/EntryFileV2;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/Entry;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDiff (J)Lorg/fdroid/index/v2/EntryFileV2;
+ public final fun getDiffs ()Ljava/util/Map;
+ public final fun getIndex ()Lorg/fdroid/index/v2/EntryFileV2;
+ public final fun getMaxAge ()Ljava/lang/Integer;
+ public final fun getTimestamp ()J
+ public final fun getVersion ()J
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/Entry$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/Entry$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/Entry;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/Entry;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/Entry$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/EntryFileV2 : org/fdroid/IndexFile {
+ public static final field Companion Lorg/fdroid/index/v2/EntryFileV2$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;I)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()J
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()I
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;I)Lorg/fdroid/index/v2/EntryFileV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/EntryFileV2;Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;IILjava/lang/Object;)Lorg/fdroid/index/v2/EntryFileV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getIpfsCidV1 ()Ljava/lang/String;
+ public fun getName ()Ljava/lang/String;
+ public final fun getNumPackages ()I
+ public fun getSha256 ()Ljava/lang/String;
+ public fun getSize ()Ljava/lang/Long;
+ public fun hashCode ()I
+ public fun serialize ()Ljava/lang/String;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/EntryFileV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/EntryFileV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/EntryFileV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/EntryFileV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/EntryFileV2$Companion {
+ public final fun deserialize (Ljava/lang/String;)Lorg/fdroid/index/v2/EntryFileV2;
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FeatureV2 {
+ public static final field Companion Lorg/fdroid/index/v2/FeatureV2$Companion;
+ public fun (Ljava/lang/String;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;)Lorg/fdroid/index/v2/FeatureV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/FeatureV2;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v2/FeatureV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/FeatureV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/FeatureV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/FeatureV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/FeatureV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FeatureV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FileV1 : org/fdroid/IndexFile {
+ public static final field Companion Lorg/fdroid/index/v2/FileV1$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Long;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;)Lorg/fdroid/index/v2/FileV1;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/FileV1;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v2/FileV1;
+ public static final fun deserialize (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV1;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getIpfsCidV1 ()Ljava/lang/String;
+ public fun getName ()Ljava/lang/String;
+ public fun getSha256 ()Ljava/lang/String;
+ public fun getSize ()Ljava/lang/Long;
+ public fun hashCode ()I
+ public fun serialize ()Ljava/lang/String;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/FileV1$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/FileV1$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/FileV1;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/FileV1;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FileV1$Companion {
+ public final fun deserialize (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV1;
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FileV2 : org/fdroid/IndexFile {
+ public static final field Companion Lorg/fdroid/index/v2/FileV2$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun component3 ()Ljava/lang/Long;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;)Lorg/fdroid/index/v2/FileV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/FileV2;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v2/FileV2;
+ public static final fun deserialize (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public static final fun fromPath (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV2;
+ public fun getIpfsCidV1 ()Ljava/lang/String;
+ public fun getName ()Ljava/lang/String;
+ public fun getSha256 ()Ljava/lang/String;
+ public fun getSize ()Ljava/lang/Long;
+ public fun hashCode ()I
+ public fun serialize ()Ljava/lang/String;
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/FileV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/FileV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/FileV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/FileV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/FileV2$Companion {
+ public final fun deserialize (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV2;
+ public final fun fromPath (Ljava/lang/String;)Lorg/fdroid/index/v2/FileV2;
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/IndexV2 {
+ public static final field Companion Lorg/fdroid/index/v2/IndexV2$Companion;
+ public fun (Lorg/fdroid/index/v2/RepoV2;Ljava/util/Map;)V
+ public synthetic fun (Lorg/fdroid/index/v2/RepoV2;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lorg/fdroid/index/v2/RepoV2;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun copy (Lorg/fdroid/index/v2/RepoV2;Ljava/util/Map;)Lorg/fdroid/index/v2/IndexV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/IndexV2;Lorg/fdroid/index/v2/RepoV2;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/IndexV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getPackages ()Ljava/util/Map;
+ public final fun getRepo ()Lorg/fdroid/index/v2/RepoV2;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun walkFiles (Lkotlin/jvm/functions/Function1;)V
+}
+
+public final synthetic class org/fdroid/index/v2/IndexV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/IndexV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/IndexV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/IndexV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/IndexV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class org/fdroid/index/v2/IndexV2DiffStreamReceiver {
+ public abstract fun onStreamEnded ()V
+ public abstract fun receivePackageMetadataDiff (Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V
+ public abstract fun receiveRepoDiff (JLkotlinx/serialization/json/JsonObject;)V
+ public abstract fun receiveVersionsDiff (Ljava/lang/String;Ljava/util/Map;)V
+}
+
+public abstract interface class org/fdroid/index/v2/IndexV2StreamReceiver {
+ public abstract fun onStreamEnded ()V
+ public abstract fun receive (Ljava/lang/String;Lorg/fdroid/index/v2/PackageV2;)V
+ public abstract fun receive (Lorg/fdroid/index/v2/RepoV2;J)V
+}
+
+public final class org/fdroid/index/v2/ManifestV2 : org/fdroid/index/v2/PackageManifest {
+ public static final field Companion Lorg/fdroid/index/v2/ManifestV2$Companion;
+ public fun (Ljava/lang/String;JLorg/fdroid/index/v2/UsesSdkV2;Ljava/lang/Integer;Lorg/fdroid/index/v2/SignerV2;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)V
+ public synthetic fun (Ljava/lang/String;JLorg/fdroid/index/v2/UsesSdkV2;Ljava/lang/Integer;Lorg/fdroid/index/v2/SignerV2;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()J
+ public final fun component3 ()Lorg/fdroid/index/v2/UsesSdkV2;
+ public final fun component4 ()Ljava/lang/Integer;
+ public final fun component5 ()Lorg/fdroid/index/v2/SignerV2;
+ public final fun component6 ()Ljava/util/List;
+ public final fun component7 ()Ljava/util/List;
+ public final fun component8 ()Ljava/util/List;
+ public final fun component9 ()Ljava/util/List;
+ public final fun copy (Ljava/lang/String;JLorg/fdroid/index/v2/UsesSdkV2;Ljava/lang/Integer;Lorg/fdroid/index/v2/SignerV2;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;)Lorg/fdroid/index/v2/ManifestV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/ManifestV2;Ljava/lang/String;JLorg/fdroid/index/v2/UsesSdkV2;Ljava/lang/Integer;Lorg/fdroid/index/v2/SignerV2;Ljava/util/List;Ljava/util/List;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lorg/fdroid/index/v2/ManifestV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getFeatureNames ()Ljava/util/List;
+ public final fun getFeatures ()Ljava/util/List;
+ public fun getMaxSdkVersion ()Ljava/lang/Integer;
+ public fun getMinSdkVersion ()Ljava/lang/Integer;
+ public fun getNativecode ()Ljava/util/List;
+ public final fun getSigner ()Lorg/fdroid/index/v2/SignerV2;
+ public fun getTargetSdkVersion ()Ljava/lang/Integer;
+ public final fun getUsesPermission ()Ljava/util/List;
+ public final fun getUsesPermissionSdk23 ()Ljava/util/List;
+ public final fun getUsesSdk ()Lorg/fdroid/index/v2/UsesSdkV2;
+ public final fun getVersionCode ()J
+ public final fun getVersionName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/ManifestV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/ManifestV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/ManifestV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/ManifestV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/ManifestV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/MetadataV2 {
+ public static final field Companion Lorg/fdroid/index/v2/MetadataV2$Companion;
+ public fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Lorg/fdroid/index/v2/Screenshots;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Lorg/fdroid/index/v2/Screenshots;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component10 ()Ljava/lang/String;
+ public final fun component11 ()Ljava/lang/String;
+ public final fun component12 ()Ljava/lang/String;
+ public final fun component13 ()Ljava/util/List;
+ public final fun component14 ()Ljava/lang/String;
+ public final fun component15 ()Ljava/lang/String;
+ public final fun component16 ()Ljava/lang/String;
+ public final fun component17 ()Ljava/lang/String;
+ public final fun component18 ()Ljava/util/List;
+ public final fun component19 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component20 ()Ljava/lang/String;
+ public final fun component21 ()Ljava/lang/String;
+ public final fun component22 ()Ljava/lang/String;
+ public final fun component23 ()Ljava/lang/String;
+ public final fun component24 ()Ljava/lang/String;
+ public final fun component25 ()Ljava/util/Map;
+ public final fun component26 ()Ljava/util/Map;
+ public final fun component27 ()Ljava/util/Map;
+ public final fun component28 ()Ljava/util/Map;
+ public final fun component29 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/util/Map;
+ public final fun component30 ()Lorg/fdroid/index/v2/Screenshots;
+ public final fun component4 ()J
+ public final fun component5 ()J
+ public final fun component6 ()Ljava/lang/String;
+ public final fun component7 ()Ljava/lang/String;
+ public final fun component8 ()Ljava/lang/String;
+ public final fun component9 ()Ljava/lang/String;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Lorg/fdroid/index/v2/Screenshots;)Lorg/fdroid/index/v2/MetadataV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/MetadataV2;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Lorg/fdroid/index/v2/Screenshots;ILjava/lang/Object;)Lorg/fdroid/index/v2/MetadataV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAdded ()J
+ public final fun getAuthorEmail ()Ljava/lang/String;
+ public final fun getAuthorName ()Ljava/lang/String;
+ public final fun getAuthorPhone ()Ljava/lang/String;
+ public final fun getAuthorWebSite ()Ljava/lang/String;
+ public final fun getBitcoin ()Ljava/lang/String;
+ public final fun getCategories ()Ljava/util/List;
+ public final fun getChangelog ()Ljava/lang/String;
+ public final fun getDescription ()Ljava/util/Map;
+ public final fun getDonate ()Ljava/util/List;
+ public final fun getFeatureGraphic ()Ljava/util/Map;
+ public final fun getFlattrID ()Ljava/lang/String;
+ public final fun getIcon ()Ljava/util/Map;
+ public final fun getIssueTracker ()Ljava/lang/String;
+ public final fun getLastUpdated ()J
+ public final fun getLiberapay ()Ljava/lang/String;
+ public final fun getLiberapayID ()Ljava/lang/String;
+ public final fun getLicense ()Ljava/lang/String;
+ public final fun getLitecoin ()Ljava/lang/String;
+ public final fun getName ()Ljava/util/Map;
+ public final fun getOpenCollective ()Ljava/lang/String;
+ public final fun getPreferredSigner ()Ljava/lang/String;
+ public final fun getPromoGraphic ()Ljava/util/Map;
+ public final fun getScreenshots ()Lorg/fdroid/index/v2/Screenshots;
+ public final fun getSourceCode ()Ljava/lang/String;
+ public final fun getSummary ()Ljava/util/Map;
+ public final fun getTranslation ()Ljava/lang/String;
+ public final fun getTvBanner ()Ljava/util/Map;
+ public final fun getVideo ()Ljava/util/Map;
+ public final fun getWebSite ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun walkFiles (Lkotlin/jvm/functions/Function1;)V
+}
+
+public final synthetic class org/fdroid/index/v2/MetadataV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/MetadataV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/MetadataV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/MetadataV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/MetadataV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/MirrorV2 {
+ public static final field Companion Lorg/fdroid/index/v2/MirrorV2$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/String;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/String;
+ public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lorg/fdroid/index/v2/MirrorV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/MirrorV2;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lorg/fdroid/index/v2/MirrorV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getCountryCode ()Ljava/lang/String;
+ public final fun getUrl ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/MirrorV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/MirrorV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/MirrorV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/MirrorV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/MirrorV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public abstract interface class org/fdroid/index/v2/PackageManifest {
+ public abstract fun getFeatureNames ()Ljava/util/List;
+ public abstract fun getMaxSdkVersion ()Ljava/lang/Integer;
+ public abstract fun getMinSdkVersion ()Ljava/lang/Integer;
+ public abstract fun getNativecode ()Ljava/util/List;
+ public abstract fun getTargetSdkVersion ()Ljava/lang/Integer;
+}
+
+public final class org/fdroid/index/v2/PackageV2 {
+ public static final field Companion Lorg/fdroid/index/v2/PackageV2$Companion;
+ public fun (Lorg/fdroid/index/v2/MetadataV2;Ljava/util/Map;)V
+ public synthetic fun (Lorg/fdroid/index/v2/MetadataV2;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Lorg/fdroid/index/v2/MetadataV2;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun copy (Lorg/fdroid/index/v2/MetadataV2;Ljava/util/Map;)Lorg/fdroid/index/v2/PackageV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/PackageV2;Lorg/fdroid/index/v2/MetadataV2;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/PackageV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMetadata ()Lorg/fdroid/index/v2/MetadataV2;
+ public final fun getVersions ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun walkFiles (Lkotlin/jvm/functions/Function1;)V
+}
+
+public final synthetic class org/fdroid/index/v2/PackageV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/PackageV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/PackageV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/PackageV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/PackageV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/PackageV2Kt {
+ public static final field ANTI_FEATURE_KNOWN_VULNERABILITY Ljava/lang/String;
+}
+
+public abstract interface class org/fdroid/index/v2/PackageVersion {
+ public abstract fun getAdded ()J
+ public abstract fun getHasKnownVulnerability ()Z
+ public abstract fun getPackageManifest ()Lorg/fdroid/index/v2/PackageManifest;
+ public abstract fun getReleaseChannels ()Ljava/util/List;
+ public abstract fun getSigner ()Lorg/fdroid/index/v2/SignerV2;
+ public abstract fun getSize ()Ljava/lang/Long;
+ public abstract fun getVersionCode ()J
+ public abstract fun getVersionName ()Ljava/lang/String;
+}
+
+public final class org/fdroid/index/v2/PackageVersionV2 : org/fdroid/index/v2/PackageVersion {
+ public static final field Companion Lorg/fdroid/index/v2/PackageVersionV2$Companion;
+ public fun (JLorg/fdroid/index/v2/FileV1;Lorg/fdroid/index/v2/FileV2;Lorg/fdroid/index/v2/ManifestV2;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (JLorg/fdroid/index/v2/FileV1;Lorg/fdroid/index/v2/FileV2;Lorg/fdroid/index/v2/ManifestV2;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()J
+ public final fun component2 ()Lorg/fdroid/index/v2/FileV1;
+ public final fun component3 ()Lorg/fdroid/index/v2/FileV2;
+ public final fun component4 ()Lorg/fdroid/index/v2/ManifestV2;
+ public final fun component5 ()Ljava/util/List;
+ public final fun component6 ()Ljava/util/Map;
+ public final fun component7 ()Ljava/util/Map;
+ public final fun copy (JLorg/fdroid/index/v2/FileV1;Lorg/fdroid/index/v2/FileV2;Lorg/fdroid/index/v2/ManifestV2;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/PackageVersionV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/PackageVersionV2;JLorg/fdroid/index/v2/FileV1;Lorg/fdroid/index/v2/FileV2;Lorg/fdroid/index/v2/ManifestV2;Ljava/util/List;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/PackageVersionV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public fun getAdded ()J
+ public final fun getAntiFeatures ()Ljava/util/Map;
+ public final fun getFile ()Lorg/fdroid/index/v2/FileV1;
+ public fun getHasKnownVulnerability ()Z
+ public final fun getManifest ()Lorg/fdroid/index/v2/ManifestV2;
+ public fun getPackageManifest ()Lorg/fdroid/index/v2/PackageManifest;
+ public fun getReleaseChannels ()Ljava/util/List;
+ public fun getSigner ()Lorg/fdroid/index/v2/SignerV2;
+ public fun getSize ()Ljava/lang/Long;
+ public final fun getSrc ()Lorg/fdroid/index/v2/FileV2;
+ public fun getVersionCode ()J
+ public fun getVersionName ()Ljava/lang/String;
+ public final fun getWhatsNew ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun walkFiles (Lkotlin/jvm/functions/Function1;)V
+}
+
+public final synthetic class org/fdroid/index/v2/PackageVersionV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/PackageVersionV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/PackageVersionV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/PackageVersionV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/PackageVersionV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/PermissionV2 {
+ public static final field Companion Lorg/fdroid/index/v2/PermissionV2$Companion;
+ public fun (Ljava/lang/String;Ljava/lang/Integer;)V
+ public synthetic fun (Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/lang/String;
+ public final fun component2 ()Ljava/lang/Integer;
+ public final fun copy (Ljava/lang/String;Ljava/lang/Integer;)Lorg/fdroid/index/v2/PermissionV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/PermissionV2;Ljava/lang/String;Ljava/lang/Integer;ILjava/lang/Object;)Lorg/fdroid/index/v2/PermissionV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMaxSdkVersion ()Ljava/lang/Integer;
+ public final fun getName ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/PermissionV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/PermissionV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/PermissionV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/PermissionV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/PermissionV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/ReleaseChannelV2 {
+ public static final field Companion Lorg/fdroid/index/v2/ReleaseChannelV2$Companion;
+ public fun (Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/ReleaseChannelV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/ReleaseChannelV2;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/ReleaseChannelV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getDescription ()Ljava/util/Map;
+ public final fun getName ()Ljava/util/Map;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/ReleaseChannelV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/ReleaseChannelV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/ReleaseChannelV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/ReleaseChannelV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/ReleaseChannelV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/RepoV2 {
+ public static final field Companion Lorg/fdroid/index/v2/RepoV2$Companion;
+ public fun (Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;JLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;JLjava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component10 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/lang/String;
+ public final fun component4 ()Ljava/lang/String;
+ public final fun component5 ()Ljava/util/Map;
+ public final fun component6 ()Ljava/util/List;
+ public final fun component7 ()J
+ public final fun component8 ()Ljava/util/Map;
+ public final fun component9 ()Ljava/util/Map;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;JLjava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/RepoV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/RepoV2;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Ljava/util/List;JLjava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/RepoV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getAddress ()Ljava/lang/String;
+ public final fun getAntiFeatures ()Ljava/util/Map;
+ public final fun getCategories ()Ljava/util/Map;
+ public final fun getDescription ()Ljava/util/Map;
+ public final fun getIcon ()Ljava/util/Map;
+ public final fun getMirrors ()Ljava/util/List;
+ public final fun getName ()Ljava/util/Map;
+ public final fun getReleaseChannels ()Ljava/util/Map;
+ public final fun getTimestamp ()J
+ public final fun getWebBaseUrl ()Ljava/lang/String;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+ public final fun walkFiles (Lkotlin/jvm/functions/Function1;)V
+}
+
+public final synthetic class org/fdroid/index/v2/RepoV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/RepoV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/RepoV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/RepoV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/RepoV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/Screenshots {
+ public static final field Companion Lorg/fdroid/index/v2/Screenshots$Companion;
+ public fun ()V
+ public fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V
+ public synthetic fun (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/Map;
+ public final fun component2 ()Ljava/util/Map;
+ public final fun component3 ()Ljava/util/Map;
+ public final fun component4 ()Ljava/util/Map;
+ public final fun component5 ()Ljava/util/Map;
+ public final fun copy (Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)Lorg/fdroid/index/v2/Screenshots;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/Screenshots;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;ILjava/lang/Object;)Lorg/fdroid/index/v2/Screenshots;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getPhone ()Ljava/util/Map;
+ public final fun getSevenInch ()Ljava/util/Map;
+ public final fun getTenInch ()Ljava/util/Map;
+ public final fun getTv ()Ljava/util/Map;
+ public final fun getWear ()Ljava/util/Map;
+ public fun hashCode ()I
+ public final fun isNull ()Z
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/Screenshots$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/Screenshots$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/Screenshots;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/Screenshots;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/Screenshots$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/SignerV2 {
+ public static final field Companion Lorg/fdroid/index/v2/SignerV2$Companion;
+ public fun (Ljava/util/List;Z)V
+ public synthetic fun (Ljava/util/List;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
+ public final fun component1 ()Ljava/util/List;
+ public final fun component2 ()Z
+ public final fun copy (Ljava/util/List;Z)Lorg/fdroid/index/v2/SignerV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/SignerV2;Ljava/util/List;ZILjava/lang/Object;)Lorg/fdroid/index/v2/SignerV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getHasMultipleSigners ()Z
+ public final fun getSha256 ()Ljava/util/List;
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/SignerV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/SignerV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/SignerV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/SignerV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/SignerV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/UsesSdkV2 {
+ public static final field Companion Lorg/fdroid/index/v2/UsesSdkV2$Companion;
+ public fun (II)V
+ public final fun component1 ()I
+ public final fun component2 ()I
+ public final fun copy (II)Lorg/fdroid/index/v2/UsesSdkV2;
+ public static synthetic fun copy$default (Lorg/fdroid/index/v2/UsesSdkV2;IIILjava/lang/Object;)Lorg/fdroid/index/v2/UsesSdkV2;
+ public fun equals (Ljava/lang/Object;)Z
+ public final fun getMinSdkVersion ()I
+ public final fun getTargetSdkVersion ()I
+ public fun hashCode ()I
+ public fun toString ()Ljava/lang/String;
+}
+
+public final synthetic class org/fdroid/index/v2/UsesSdkV2$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
+ public static final field INSTANCE Lorg/fdroid/index/v2/UsesSdkV2$$serializer;
+ public final fun childSerializers ()[Lkotlinx/serialization/KSerializer;
+ public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
+ public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lorg/fdroid/index/v2/UsesSdkV2;
+ public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
+ public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
+ public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lorg/fdroid/index/v2/UsesSdkV2;)V
+ public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
+}
+
+public final class org/fdroid/index/v2/UsesSdkV2$Companion {
+ public final fun serializer ()Lkotlinx/serialization/KSerializer;
+}
+
diff --git a/libs/index/build.gradle.kts b/libs/index/build.gradle.kts
index 518b1c333..9125011f6 100644
--- a/libs/index/build.gradle.kts
+++ b/libs/index/build.gradle.kts
@@ -13,6 +13,7 @@ kotlin {
@OptIn(org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation::class)
abiValidation { enabled = true }
+ jvm()
android {
namespace = "org.fdroid.index"
compileSdk = libs.versions.compileSdk.get().toInt()
@@ -26,7 +27,6 @@ kotlin {
}
}
- withJava()
compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 }
withHostTest {
@@ -54,7 +54,6 @@ kotlin {
implementation(libs.goncalossilva.resources)
}
}
- // JVM is disabled for now, because Android app is including it instead of Android library
jvmMain { dependencies {} }
jvmTest { dependencies { implementation(libs.junit) } }
androidMain {
diff --git a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt
index ddfb6d0e6..a8c68f516 100644
--- a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt
+++ b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v1/IndexV1StreamProcessorTest.kt
@@ -9,7 +9,6 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertNull
import kotlin.test.fail
import kotlinx.serialization.SerializationException
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.v2.AntiFeatureV2
import org.fdroid.index.v2.CategoryV2
import org.fdroid.index.v2.IndexV2
@@ -22,6 +21,7 @@ import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
+import org.fdroid.test.TestUtils.ASSET_PATH
import org.fdroid.test.v1compat
import org.junit.Test
diff --git a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/IndexV2FullStreamProcessorTest.kt b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/IndexV2FullStreamProcessorTest.kt
index b201a69a5..224415880 100644
--- a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/IndexV2FullStreamProcessorTest.kt
+++ b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/IndexV2FullStreamProcessorTest.kt
@@ -10,11 +10,11 @@ import kotlin.test.assertTrue
import kotlin.test.fail
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
-import org.fdroid.index.ASSET_PATH
import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
import org.fdroid.test.TestDataMidV2
import org.fdroid.test.TestDataMinV2
+import org.fdroid.test.TestUtils.ASSET_PATH
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
diff --git a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt
index dde6aab45..d23ea0dab 100644
--- a/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt
+++ b/libs/index/src/androidHostTest/kotlin/org/fdroid/index/v2/ReflectionDifferTest.kt
@@ -13,7 +13,6 @@ import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.jsonObject
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.IndexParser
import org.fdroid.index.IndexParser.json
import org.fdroid.index.parseV2
@@ -22,6 +21,7 @@ import org.fdroid.test.DiffUtils.cleanMetadata
import org.fdroid.test.DiffUtils.cleanRepo
import org.fdroid.test.DiffUtils.cleanVersion
import org.fdroid.test.LOCALE
+import org.fdroid.test.TestUtils.ASSET_PATH
internal class ReflectionDifferTest {
diff --git a/libs/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt b/libs/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt
index 27189fbbe..de86ec4ae 100644
--- a/libs/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt
+++ b/libs/index/src/commonTest/kotlin/org/fdroid/index/IndexConverterTest.kt
@@ -12,28 +12,26 @@ import org.fdroid.test.TestDataMinV2
import org.fdroid.test.TestUtils.sorted
import org.fdroid.test.v1compat
-internal const val ASSET_PATH = "../sharedTest/src/main/assets"
-
internal class IndexConverterTest {
@Test
fun testEmpty() {
- testConversation("$ASSET_PATH/index-empty-v1.json", TestDataEmptyV2.index.v1compat())
+ testConversation("index-empty-v1.json", TestDataEmptyV2.index.v1compat())
}
@Test
fun testMin() {
- testConversation("$ASSET_PATH/index-min-v1.json", TestDataMinV2.index.v1compat())
+ testConversation("index-min-v1.json", TestDataMinV2.index.v1compat())
}
@Test
fun testMid() {
- testConversation("$ASSET_PATH/index-mid-v1.json", TestDataMidV2.indexCompat)
+ testConversation("index-mid-v1.json", TestDataMidV2.indexCompat)
}
@Test
fun testMax() {
- testConversation("$ASSET_PATH/index-max-v1.json", TestDataMaxV2.indexCompat)
+ testConversation("index-max-v1.json", TestDataMaxV2.indexCompat)
}
private fun testConversation(file: String, expectedIndex: IndexV2) {
diff --git a/libs/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt b/libs/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt
index 49b393c4f..25e242d97 100644
--- a/libs/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt
+++ b/libs/index/src/commonTest/kotlin/org/fdroid/index/v1/IndexV1Test.kt
@@ -6,7 +6,6 @@ import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlinx.serialization.SerializationException
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.IndexParser.parseV1
import org.fdroid.test.TestDataEmptyV1
import org.fdroid.test.TestDataMaxV1
@@ -17,7 +16,7 @@ internal class IndexV1Test {
@Test
fun testIndexEmptyV1() {
- val indexRes = Resource("$ASSET_PATH/index-empty-v1.json")
+ val indexRes = Resource("index-empty-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataEmptyV1.index, index)
@@ -25,7 +24,7 @@ internal class IndexV1Test {
@Test
fun testIndexMinV1() {
- val indexRes = Resource("$ASSET_PATH/index-min-v1.json")
+ val indexRes = Resource("index-min-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMinV1.index, index)
@@ -33,7 +32,7 @@ internal class IndexV1Test {
@Test
fun testIndexMidV1() {
- val indexRes = Resource("$ASSET_PATH/index-mid-v1.json")
+ val indexRes = Resource("index-mid-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMidV1.index, index)
@@ -41,7 +40,7 @@ internal class IndexV1Test {
@Test
fun testIndexMaxV1() {
- val indexRes = Resource("$ASSET_PATH/index-max-v1.json")
+ val indexRes = Resource("index-max-v1.json")
val indexStr = indexRes.readText()
val index = parseV1(indexStr)
assertEquals(TestDataMaxV1.index, index)
@@ -123,14 +122,14 @@ internal class IndexV1Test {
@Test
fun testGuardianProjectV1() {
- val indexRes = Resource("$ASSET_PATH/guardianproject_index-v1.json")
+ val indexRes = Resource("guardianproject_index-v1.json")
val indexStr = indexRes.readText()
parseV1(indexStr)
}
@Test
fun testLocalizedV1() {
- val indexRes = Resource("$ASSET_PATH/localized.json")
+ val indexRes = Resource("localized.json")
val indexStr = indexRes.readText()
parseV1(indexStr)
}
diff --git a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/EntryTest.kt b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/EntryTest.kt
index 67b6764e7..0a8719169 100644
--- a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/EntryTest.kt
+++ b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/EntryTest.kt
@@ -5,7 +5,6 @@ import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlinx.serialization.SerializationException
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.IndexParser
import org.fdroid.test.TestDataEntry
import org.junit.Test
@@ -14,22 +13,22 @@ internal class EntryTest {
@Test
fun testEmpty() {
- testEntryEquality("$ASSET_PATH/entry-empty-v2.json", TestDataEntry.empty)
+ testEntryEquality("entry-empty-v2.json", TestDataEntry.empty)
}
@Test
fun testEmptyToMin() {
- testEntryEquality("$ASSET_PATH/diff-empty-min/$DATA_FILE_NAME", TestDataEntry.emptyToMin)
+ testEntryEquality("diff-empty-min/$DATA_FILE_NAME", TestDataEntry.emptyToMin)
}
@Test
fun testEmptyToMid() {
- testEntryEquality("$ASSET_PATH/diff-empty-mid/$DATA_FILE_NAME", TestDataEntry.emptyToMid)
+ testEntryEquality("diff-empty-mid/$DATA_FILE_NAME", TestDataEntry.emptyToMid)
}
@Test
fun testEmptyToMax() {
- testEntryEquality("$ASSET_PATH/diff-empty-max/$DATA_FILE_NAME", TestDataEntry.emptyToMax)
+ testEntryEquality("diff-empty-max/$DATA_FILE_NAME", TestDataEntry.emptyToMax)
}
@Test
diff --git a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2DiffStreamProcessorTest.kt b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2DiffStreamProcessorTest.kt
index 344a55ad9..f13f50bff 100644
--- a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2DiffStreamProcessorTest.kt
+++ b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2DiffStreamProcessorTest.kt
@@ -10,8 +10,8 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.IndexParser
+import org.fdroid.test.TestUtils.ASSET_PATH
import org.junit.Test
internal class IndexV2DiffStreamProcessorTest {
diff --git a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt
index 6227722cf..1b994386e 100644
--- a/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt
+++ b/libs/index/src/commonTest/kotlin/org/fdroid/index/v2/IndexV2Test.kt
@@ -6,7 +6,6 @@ import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlinx.serialization.SerializationException
-import org.fdroid.index.ASSET_PATH
import org.fdroid.index.IndexParser.parseV2
import org.fdroid.test.TestDataEmptyV2
import org.fdroid.test.TestDataMaxV2
@@ -17,27 +16,27 @@ internal class IndexV2Test {
@Test
fun testEmpty() {
- testIndexEquality("$ASSET_PATH/index-empty-v2.json", TestDataEmptyV2.index)
+ testIndexEquality("index-empty-v2.json", TestDataEmptyV2.index)
}
@Test
fun testMin() {
- testIndexEquality("$ASSET_PATH/index-min-v2.json", TestDataMinV2.index)
+ testIndexEquality("index-min-v2.json", TestDataMinV2.index)
}
@Test
fun testMinReordered() {
- testIndexEquality("$ASSET_PATH/index-min-reordered-v2.json", TestDataMinV2.index)
+ testIndexEquality("index-min-reordered-v2.json", TestDataMinV2.index)
}
@Test
fun testMid() {
- testIndexEquality("$ASSET_PATH/index-mid-v2.json", TestDataMidV2.index)
+ testIndexEquality("index-mid-v2.json", TestDataMidV2.index)
}
@Test
fun testMax() {
- testIndexEquality("$ASSET_PATH/index-max-v2.json", TestDataMaxV2.index)
+ testIndexEquality("index-max-v2.json", TestDataMaxV2.index)
}
@Test
diff --git a/libs/sharedTest/build.gradle.kts b/libs/sharedTest/build.gradle.kts
index c2535f684..4c79f64c7 100644
--- a/libs/sharedTest/build.gradle.kts
+++ b/libs/sharedTest/build.gradle.kts
@@ -1,28 +1,29 @@
plugins {
- alias(libs.plugins.jetbrains.kotlin.android)
- alias(libs.plugins.android.library)
+ alias(libs.plugins.jetbrains.kotlin.multiplatform)
+ alias(libs.plugins.android.multiplatform.library)
alias(libs.plugins.ktfmt)
}
-// not really an Android library, but index is not publishing for JVM at the moment
-android {
- namespace = "org.fdroid.test"
- compileSdk = libs.versions.compileSdk.get().toInt()
- defaultConfig { minSdk = 21 }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_17
- targetCompatibility = JavaVersion.VERSION_17
+kotlin {
+ jvm()
+ android {
+ namespace = "org.fdroid.test"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+ minSdk = 21
+ compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 }
+ }
+ sourceSets {
+ commonMain {
+ resources.srcDir("src/commonMain/resources")
+ dependencies {
+ implementation(project(":libs:download"))
+ implementation(project(":libs:index"))
+
+ implementation(libs.kotlin.test)
+ implementation(libs.kotlinx.serialization.json)
+ }
+ }
}
}
-kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 } }
-
-dependencies {
- implementation(project(":libs:download"))
- implementation(project(":libs:index"))
-
- implementation(libs.kotlin.test)
- implementation(libs.kotlinx.serialization.json)
-}
-
ktfmt { googleStyle() }
diff --git a/libs/sharedTest/src/main/AndroidManifest.xml b/libs/sharedTest/src/commonMain/AndroidManifest.xml
similarity index 100%
rename from libs/sharedTest/src/main/AndroidManifest.xml
rename to libs/sharedTest/src/commonMain/AndroidManifest.xml
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/DiffUtils.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/DiffUtils.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/DiffUtils.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/DiffUtils.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestAppUtils.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestAppUtils.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestAppUtils.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestAppUtils.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataEntry.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataEntry.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataEntry.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataEntry.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataV1.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataV1.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataV1.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataV1.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataV2.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataV2.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestDataV2.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestDataV2.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestRepoUtils.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestRepoUtils.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestRepoUtils.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestRepoUtils.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestUtils.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestUtils.kt
similarity index 88%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestUtils.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestUtils.kt
index 7fae843de..f4dc131b3 100644
--- a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestUtils.kt
+++ b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestUtils.kt
@@ -1,5 +1,6 @@
package org.fdroid.test
+import java.io.InputStream
import kotlin.random.Random
import org.fdroid.index.v2.IndexV2
import org.fdroid.index.v2.LocalizedFileListV2
@@ -8,6 +9,13 @@ import org.fdroid.index.v2.Screenshots
object TestUtils {
+ const val ASSET_PATH = "../sharedTest/src/commonMain/resources"
+
+ fun getRes(file: String): InputStream {
+ val classLoader = TestUtils.javaClass.classLoader ?: error("no classloader")
+ return classLoader.getResourceAsStream(file) ?: error("no resource $file")
+ }
+
fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2).map { it.toInt(16).toByte() }.toByteArray()
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/TestVersionUtils.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestVersionUtils.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/TestVersionUtils.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/TestVersionUtils.kt
diff --git a/libs/sharedTest/src/main/kotlin/org/fdroid/test/VerifierConstants.kt b/libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/VerifierConstants.kt
similarity index 100%
rename from libs/sharedTest/src/main/kotlin/org/fdroid/test/VerifierConstants.kt
rename to libs/sharedTest/src/commonMain/kotlin/org/fdroid/test/VerifierConstants.kt
diff --git a/libs/sharedTest/src/main/assets/diff-empty-max/1337.json b/libs/sharedTest/src/commonMain/resources/diff-empty-max/1337.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-max/1337.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-max/1337.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-max/23.json b/libs/sharedTest/src/commonMain/resources/diff-empty-max/23.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-max/23.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-max/23.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-max/42.json b/libs/sharedTest/src/commonMain/resources/diff-empty-max/42.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-max/42.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-max/42.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-max/entry.jar b/libs/sharedTest/src/commonMain/resources/diff-empty-max/entry.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-max/entry.jar
rename to libs/sharedTest/src/commonMain/resources/diff-empty-max/entry.jar
diff --git a/libs/sharedTest/src/main/assets/diff-empty-max/entry.json b/libs/sharedTest/src/commonMain/resources/diff-empty-max/entry.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-max/entry.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-max/entry.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-mid/23.json b/libs/sharedTest/src/commonMain/resources/diff-empty-mid/23.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-mid/23.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-mid/23.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-mid/42.json b/libs/sharedTest/src/commonMain/resources/diff-empty-mid/42.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-mid/42.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-mid/42.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-mid/entry.jar b/libs/sharedTest/src/commonMain/resources/diff-empty-mid/entry.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-mid/entry.jar
rename to libs/sharedTest/src/commonMain/resources/diff-empty-mid/entry.jar
diff --git a/libs/sharedTest/src/main/assets/diff-empty-mid/entry.json b/libs/sharedTest/src/commonMain/resources/diff-empty-mid/entry.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-mid/entry.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-mid/entry.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-min/23.json b/libs/sharedTest/src/commonMain/resources/diff-empty-min/23.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-min/23.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-min/23.json
diff --git a/libs/sharedTest/src/main/assets/diff-empty-min/entry.jar b/libs/sharedTest/src/commonMain/resources/diff-empty-min/entry.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-min/entry.jar
rename to libs/sharedTest/src/commonMain/resources/diff-empty-min/entry.jar
diff --git a/libs/sharedTest/src/main/assets/diff-empty-min/entry.json b/libs/sharedTest/src/commonMain/resources/diff-empty-min/entry.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/diff-empty-min/entry.json
rename to libs/sharedTest/src/commonMain/resources/diff-empty-min/entry.json
diff --git a/libs/sharedTest/src/main/assets/entry-empty-v2.json b/libs/sharedTest/src/commonMain/resources/entry-empty-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/entry-empty-v2.json
rename to libs/sharedTest/src/commonMain/resources/entry-empty-v2.json
diff --git a/libs/sharedTest/src/main/assets/guardianproject_entry.jar b/libs/sharedTest/src/commonMain/resources/guardianproject_entry.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/guardianproject_entry.jar
rename to libs/sharedTest/src/commonMain/resources/guardianproject_entry.jar
diff --git a/libs/sharedTest/src/main/assets/guardianproject_index-v1.json b/libs/sharedTest/src/commonMain/resources/guardianproject_index-v1.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/guardianproject_index-v1.json
rename to libs/sharedTest/src/commonMain/resources/guardianproject_index-v1.json
diff --git a/libs/sharedTest/src/main/assets/index-empty-v1.json b/libs/sharedTest/src/commonMain/resources/index-empty-v1.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-empty-v1.json
rename to libs/sharedTest/src/commonMain/resources/index-empty-v1.json
diff --git a/libs/sharedTest/src/main/assets/index-empty-v2.json b/libs/sharedTest/src/commonMain/resources/index-empty-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-empty-v2.json
rename to libs/sharedTest/src/commonMain/resources/index-empty-v2.json
diff --git a/libs/sharedTest/src/main/assets/index-max-v1.json b/libs/sharedTest/src/commonMain/resources/index-max-v1.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-max-v1.json
rename to libs/sharedTest/src/commonMain/resources/index-max-v1.json
diff --git a/libs/sharedTest/src/main/assets/index-max-v2.json b/libs/sharedTest/src/commonMain/resources/index-max-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-max-v2.json
rename to libs/sharedTest/src/commonMain/resources/index-max-v2.json
diff --git a/libs/sharedTest/src/main/assets/index-mid-v1.json b/libs/sharedTest/src/commonMain/resources/index-mid-v1.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-mid-v1.json
rename to libs/sharedTest/src/commonMain/resources/index-mid-v1.json
diff --git a/libs/sharedTest/src/main/assets/index-mid-v2.json b/libs/sharedTest/src/commonMain/resources/index-mid-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-mid-v2.json
rename to libs/sharedTest/src/commonMain/resources/index-mid-v2.json
diff --git a/libs/sharedTest/src/main/assets/index-min-reordered-v2.json b/libs/sharedTest/src/commonMain/resources/index-min-reordered-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-min-reordered-v2.json
rename to libs/sharedTest/src/commonMain/resources/index-min-reordered-v2.json
diff --git a/libs/sharedTest/src/main/assets/index-min-v1.json b/libs/sharedTest/src/commonMain/resources/index-min-v1.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-min-v1.json
rename to libs/sharedTest/src/commonMain/resources/index-min-v1.json
diff --git a/libs/sharedTest/src/main/assets/index-min-v2.json b/libs/sharedTest/src/commonMain/resources/index-min-v2.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/index-min-v2.json
rename to libs/sharedTest/src/commonMain/resources/index-min-v2.json
diff --git a/libs/sharedTest/src/main/assets/localized.json b/libs/sharedTest/src/commonMain/resources/localized.json
similarity index 100%
rename from libs/sharedTest/src/main/assets/localized.json
rename to libs/sharedTest/src/commonMain/resources/localized.json
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_corrupt_app_package_name_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_corrupt_app_package_name_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_corrupt_app_package_name_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_corrupt_app_package_name_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_corrupt_package_name_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_corrupt_package_name_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_corrupt_package_name_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_corrupt_package_name_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_no-.RSA_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-.RSA_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_no-.RSA_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-.RSA_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_no-.SF_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-.SF_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_no-.SF_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-.SF_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_no-MANIFEST.MF_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-MANIFEST.MF_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_no-MANIFEST.MF_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-MANIFEST.MF_index-v1.jar
diff --git a/libs/sharedTest/src/main/assets/testy.at.or.at_no-signature_index-v1.jar b/libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-signature_index-v1.jar
similarity index 100%
rename from libs/sharedTest/src/main/assets/testy.at.or.at_no-signature_index-v1.jar
rename to libs/sharedTest/src/commonMain/resources/testy.at.or.at_no-signature_index-v1.jar