mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-01-31 02:01:35 -05:00
Move libraries into their own folder
and remove sharedTest symlink hack. The shared tests are now a proper gradle module to avoid issues with using the same source files in different modules.
This commit is contained in:
committed by
Michael Pöhn
parent
a6bce15116
commit
f6075848e7
145
libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt
Normal file
145
libs/database/src/dbTest/java/org/fdroid/database/AppDaoTest.kt
Normal file
@@ -0,0 +1,145 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
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.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)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetSameAppFromTwoRepos() {
|
||||
// insert same app into three repos (repoId1 has highest weight)
|
||||
val repoId2 = repoDao.insertOrReplace(getRandomRepo())
|
||||
val repoId3 = repoDao.insertOrReplace(getRandomRepo())
|
||||
val repoId1 = 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)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@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 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)
|
||||
}
|
||||
|
||||
@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"))
|
||||
|
||||
// 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"))
|
||||
}
|
||||
|
||||
@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, packageName2, app2, locales)
|
||||
assertEquals(2, appDao.getNumberOfAppsInRepository(repoId))
|
||||
|
||||
appDao.insert(repoId, packageName3, app3, locales)
|
||||
assertEquals(3, appDao.getNumberOfAppsInRepository(repoId))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.fdroid.LocaleChooser.getBestLocale
|
||||
import org.fdroid.database.AppListSortOrder.LAST_UPDATED
|
||||
import org.fdroid.database.AppListSortOrder.NAME
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.index.v2.MetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
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.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class AppListItemsTest : AppTest() {
|
||||
|
||||
private val pm: PackageManager = mockk()
|
||||
|
||||
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)
|
||||
|
||||
// nothing is installed
|
||||
every { pm.getInstalledPackages(0) } returns emptyList()
|
||||
|
||||
// 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 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])
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// nothing is installed
|
||||
every { pm.getInstalledPackages(0) } returns emptyList()
|
||||
|
||||
// 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])
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// 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 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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
@Suppress("DEPRECATION")
|
||||
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(packageInfo2.getVersionCode(), installed.installedVersionCode)
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
@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 repoId2 = repoDao.insertOrReplace(getRandomRepo())
|
||||
val repoId3 = repoDao.insertOrReplace(getRandomRepo())
|
||||
val repoId1 = 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 testOnlyFromGivenCategories() {
|
||||
// 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.getAppListItemsByName("B").getOrFail(),
|
||||
appDao.getAppListItemsByLastUpdated("B").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.getAppListItemsByName("C").getOrFail(),
|
||||
appDao.getAppListItemsByLastUpdated("C").getOrFail(),
|
||||
).forEach { apps ->
|
||||
assertEquals(0, apps.size)
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
@Suppress("DEPRECATION")
|
||||
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(packageInfo1.getVersionCode(), 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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<AppListItem>) -> Unit) {
|
||||
appDao.getAppListItemsByName().getOrFail().let(block)
|
||||
appDao.getAppListItemsByName("A").getOrFail().let(block)
|
||||
appDao.getAppListItemsByLastUpdated().getOrFail().let(block)
|
||||
appDao.getAppListItemsByLastUpdated("A").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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,298 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.fdroid.LocaleChooser.getBestLocale
|
||||
import org.fdroid.database.TestUtils.getOrAwaitValue
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.index.v2.MetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class AppOverviewItemsTest : AppTest() {
|
||||
|
||||
@Test
|
||||
fun testAntiFeatures() {
|
||||
// insert one apps with without version
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, app1, locales)
|
||||
|
||||
// without version, anti-features are empty
|
||||
appDao.getAppOverviewItems().getOrFail().let { 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)
|
||||
appDao.getAppOverviewItems().getOrFail().let { 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)
|
||||
appDao.getAppOverviewItems().getOrFail().let { 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)
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(version3.antiFeatures, apps[0].antiFeatures)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIcons() {
|
||||
// insert one app
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, app1, locales)
|
||||
|
||||
// icon is returned correctly
|
||||
appDao.getAppOverviewItems().getOrFail().let { 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)
|
||||
|
||||
// now icon is returned from app in second repo
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app2.icon.getBestLocale(locales), apps[0].getIcon(locales))
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetByRepoWeight() {
|
||||
// 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
|
||||
appDao.getAppOverviewItems().getOrFail().let { 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)
|
||||
|
||||
// now second app from second repo is returned
|
||||
appDao.getAppOverviewItems().getOrFail().let { apps ->
|
||||
assertEquals(1, apps.size)
|
||||
assertEquals(app2, 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)
|
||||
|
||||
// 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
|
||||
val app3b = app3.copy(icon = icons2)
|
||||
appDao.insert(repoId2, packageName3, app3b)
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// 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, so is not last anymore
|
||||
val app3b = app3.copy(icon = icons2)
|
||||
appDao.insert(repoId2, packageName3, app3b)
|
||||
// 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() {
|
||||
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
|
||||
assertEquals(3, appDao.getAppOverviewItems().getOrAwaitValue()?.size)
|
||||
assertEquals(3, appDao.getAppOverviewItems("A").getOrAwaitValue()?.size)
|
||||
|
||||
// only 1 app after disabling first repo
|
||||
repoDao.setRepositoryEnabled(repoId, false)
|
||||
assertEquals(1, appDao.getAppOverviewItems().getOrAwaitValue()?.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)
|
||||
assertEquals(0, appDao.getAppOverviewItems().getOrAwaitValue()?.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))
|
||||
}
|
||||
|
||||
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.summary.getBestLocale(locales), actual.summary)
|
||||
assertEquals(expected.icon.getBestLocale(locales), actual.getIcon(locales))
|
||||
}
|
||||
|
||||
}
|
||||
47
libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt
Normal file
47
libs/database/src/dbTest/java/org/fdroid/database/AppTest.kt
Normal file
@@ -0,0 +1,47 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomFileV2
|
||||
import org.fdroid.test.TestUtils.getRandomString
|
||||
import org.fdroid.test.TestUtils.sort
|
||||
import org.junit.Rule
|
||||
|
||||
internal abstract class AppTest : DbTest() {
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
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(
|
||||
name = name1,
|
||||
icon = icons1,
|
||||
summary = null,
|
||||
lastUpdated = 10,
|
||||
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(
|
||||
name = name3,
|
||||
icon = null,
|
||||
summary = name3,
|
||||
lastUpdated = 30,
|
||||
categories = listOf("A", "B")
|
||||
).sort()
|
||||
|
||||
}
|
||||
120
libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt
Normal file
120
libs/database/src/dbTest/java/org/fdroid/database/DbTest.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.AssetManager
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import androidx.room.Room
|
||||
import androidx.test.core.app.ApplicationProvider.getApplicationContext
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import org.fdroid.database.TestUtils.assertRepoEquals
|
||||
import org.fdroid.database.TestUtils.toMetadataV2
|
||||
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.sort
|
||||
import org.fdroid.test.TestUtils.sorted
|
||||
import org.fdroid.test.VerifierConstants.CERTIFICATE
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.fail
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal abstract class DbTest {
|
||||
|
||||
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
|
||||
|
||||
protected 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()
|
||||
|
||||
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)
|
||||
val streamReceiver = DbV1StreamReceiver(db, repoId) { true }
|
||||
val indexProcessor = IndexV1StreamProcessor(streamReceiver, certificate, lastTimestamp)
|
||||
db.runInTransaction {
|
||||
assets.open(indexAssetPath).use { indexStream ->
|
||||
indexProcessor.process(indexStream)
|
||||
}
|
||||
}
|
||||
return repoId
|
||||
}
|
||||
|
||||
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)
|
||||
val streamReceiver = DbV2StreamReceiver(db, repoId) { true }
|
||||
val indexProcessor = IndexV2FullStreamProcessor(streamReceiver, certificate)
|
||||
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(
|
||||
packageV2.metadata,
|
||||
appDao.getApp(repoId, packageName)?.toMetadataV2()?.sort()
|
||||
)
|
||||
val versions = versionDao.getAppVersions(repoId, packageName).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()
|
||||
assertEquals(packageVersionV2, version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.fdroid.index.RELEASE_CHANNEL_BETA
|
||||
import org.fdroid.test.TestDataMidV2
|
||||
import org.fdroid.test.TestDataMinV2
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class DbUpdateCheckerTest : DbTest() {
|
||||
|
||||
private lateinit var updateChecker: DbUpdateChecker
|
||||
private val packageManager: PackageManager = mockk()
|
||||
|
||||
private val packageInfo = PackageInfo().apply {
|
||||
packageName = TestDataMinV2.packageName
|
||||
@Suppress("DEPRECATION")
|
||||
versionCode = 0
|
||||
}
|
||||
|
||||
@Before
|
||||
override fun createDb() {
|
||||
super.createDb()
|
||||
every { packageManager.systemAvailableFeatures } returns emptyArray()
|
||||
updateChecker = DbUpdateChecker(db, packageManager)
|
||||
}
|
||||
|
||||
@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 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.packageName, appVersions[0].packageName)
|
||||
assertEquals(TestDataMinV2.version.file.sha256, appVersions[0].update.version.versionId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.apache.commons.io.input.CountingInputStream
|
||||
import org.fdroid.index.IndexConverter
|
||||
import org.fdroid.index.v1.IndexV1StreamProcessor
|
||||
import org.fdroid.index.v1.IndexV1StreamReceiver
|
||||
import org.fdroid.index.v2.AntiFeatureV2
|
||||
import org.fdroid.index.v2.CategoryV2
|
||||
import org.fdroid.index.v2.MetadataV2
|
||||
import org.fdroid.index.v2.PackageVersionV2
|
||||
import org.fdroid.index.v2.ReleaseChannelV2
|
||||
import org.fdroid.index.v2.RepoV2
|
||||
import org.fdroid.test.TestDataEmptyV1
|
||||
import org.fdroid.test.TestDataMaxV1
|
||||
import org.fdroid.test.TestDataMidV1
|
||||
import org.fdroid.test.TestDataMinV1
|
||||
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()
|
||||
|
||||
@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, null, -1)
|
||||
db.runInTransaction {
|
||||
assets.open(path).use { indexStream ->
|
||||
indexProcessor.process(indexStream)
|
||||
}
|
||||
}
|
||||
return repoId
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExceptionWhileStreamingDoesNotSaveIntoDb() {
|
||||
val cIn = CountingInputStream(assets.open("index-max-v1.json"))
|
||||
assertFailsWith<SerializationException> {
|
||||
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, null, -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)
|
||||
}
|
||||
|
||||
@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, certificate: String?) {
|
||||
streamReceiver.receive(repo, version, certificate)
|
||||
callback()
|
||||
}
|
||||
|
||||
override fun receive(packageName: String, m: MetadataV2) {
|
||||
streamReceiver.receive(packageName, m)
|
||||
callback()
|
||||
}
|
||||
|
||||
override fun receive(packageName: String, v: Map<String, PackageVersionV2>) {
|
||||
streamReceiver.receive(packageName, v)
|
||||
callback()
|
||||
}
|
||||
|
||||
override fun updateRepo(
|
||||
antiFeatures: Map<String, AntiFeatureV2>,
|
||||
categories: Map<String, CategoryV2>,
|
||||
releaseChannels: Map<String, ReleaseChannelV2>,
|
||||
) {
|
||||
streamReceiver.updateRepo(antiFeatures, categories, releaseChannels)
|
||||
callback()
|
||||
}
|
||||
|
||||
override fun updateAppMetadata(packageName: String, preferredSigner: String?) {
|
||||
streamReceiver.updateAppMetadata(packageName, preferredSigner)
|
||||
callback()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.fdroid.index.IndexParser
|
||||
import org.fdroid.index.parseV2
|
||||
import org.fdroid.index.v2.IndexV2
|
||||
import org.fdroid.index.v2.IndexV2DiffStreamProcessor
|
||||
import org.fdroid.test.TestDataMaxV2
|
||||
import org.fdroid.test.TestDataMidV2
|
||||
import org.fdroid.test.TestDataMinV2
|
||||
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
|
||||
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 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 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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}""".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 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
|
||||
fun testMinNoMetadataNoVersion() {
|
||||
val diffJson = """{
|
||||
"packages": {
|
||||
"org.fdroid.min1": {
|
||||
}
|
||||
}
|
||||
}""".trimIndent()
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = diffJson,
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testAppDenyKeyList() {
|
||||
val diffRepoIdJson = """{
|
||||
"packages": {
|
||||
"org.fdroid.min1": {
|
||||
"metadata": {
|
||||
"repoId": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}""".trimIndent()
|
||||
assertFailsWith<SerializationException> {
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = diffRepoIdJson,
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
val diffPackageNameJson = """{
|
||||
"packages": {
|
||||
"org.fdroid.min1": {
|
||||
"metadata": {
|
||||
"packageName": "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}""".trimIndent()
|
||||
assertFailsWith<SerializationException> {
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = diffPackageNameJson,
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersionsDenyKeyList() {
|
||||
assertFailsWith<SerializationException> {
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = getMinVersionJson(""""packageName": "foo""""),
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
assertFailsWith<SerializationException> {
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = getMinVersionJson(""""repoId": 1"""),
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
assertFailsWith<SerializationException> {
|
||||
testJsonDiff(
|
||||
startPath = "index-min-v2.json",
|
||||
diff = getMinVersionJson(""""versionId": "bar""""),
|
||||
endIndex = TestDataMinV2.index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMinVersionJson(insert: String) = """{
|
||||
"packages": {
|
||||
"org.fdroid.min1": {
|
||||
"versions": {
|
||||
"824a109b2352138c3699760e1683385d0ed50ce526fc7982f8d65757743374bf": {
|
||||
$insert
|
||||
}
|
||||
}
|
||||
}
|
||||
}""".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.packageName1 to TestDataMidV2.app1,
|
||||
TestDataMidV2.packageName2 to fdroidPackage,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun testJsonDiff(startPath: String, diff: String, endIndex: IndexV2) {
|
||||
testDiff(startPath, ByteArrayInputStream(diff.toByteArray()), endIndex)
|
||||
}
|
||||
|
||||
private fun testDiff(startPath: String, diffPath: String, endIndex: IndexV2) {
|
||||
testDiff(startPath, assets.open(diffPath), 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.serialization.SerializationException
|
||||
import org.apache.commons.io.input.CountingInputStream
|
||||
import org.fdroid.CompatibilityChecker
|
||||
import org.fdroid.index.v2.IndexV2FullStreamProcessor
|
||||
import org.fdroid.test.TestDataEmptyV2
|
||||
import org.fdroid.test.TestDataMaxV2
|
||||
import org.fdroid.test.TestDataMidV2
|
||||
import org.fdroid.test.TestDataMinV2
|
||||
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 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 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 testExceptionWhileStreamingDoesNotSaveIntoDb() {
|
||||
val cIn = CountingInputStream(assets.open("index-max-v2.json"))
|
||||
val compatibilityChecker = CompatibilityChecker {
|
||||
if (cIn.byteCount > 0) throw SerializationException()
|
||||
true
|
||||
}
|
||||
assertFailsWith<SerializationException> {
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.fdroid.database.TestUtils.assertRepoEquals
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
import org.fdroid.test.TestUtils.getRandomString
|
||||
import org.fdroid.test.TestUtils.orNull
|
||||
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import kotlin.random.Random
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class RepositoryDaoTest : DbTest() {
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@Test
|
||||
fun testInsertInitialRepository() {
|
||||
val repo = InitialRepository(
|
||||
name = getRandomString(),
|
||||
address = getRandomString(),
|
||||
description = getRandomString(),
|
||||
certificate = getRandomString(),
|
||||
version = Random.nextLong(),
|
||||
enabled = Random.nextBoolean(),
|
||||
weight = Random.nextInt(),
|
||||
)
|
||||
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(repo.weight, actualRepo.weight)
|
||||
assertEquals(-1, actualRepo.timestamp)
|
||||
assertEquals(emptyList(), actualRepo.mirrors)
|
||||
assertEquals(emptyList(), actualRepo.userMirrors)
|
||||
assertEquals(emptyList(), actualRepo.disabledMirrors)
|
||||
assertEquals(listOf(org.fdroid.download.Mirror(repo.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 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)
|
||||
|
||||
// insert second repo
|
||||
val repo2 = getRandomRepo()
|
||||
val repoId2 = repoDao.insertOrReplace(repo2)
|
||||
|
||||
// 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?.plus(1), 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))
|
||||
|
||||
// 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 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 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).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).size)
|
||||
assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
|
||||
// preferences are not touched by clearing
|
||||
assertEquals(repositoryPreferences, repoDao.getRepositoryPreferences(repoId))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun certGetsUpdated() {
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
assertEquals(1, repoDao.getRepositories().size)
|
||||
assertEquals(null, repoDao.getRepositories()[0].certificate)
|
||||
|
||||
val cert = getRandomString()
|
||||
repoDao.updateRepository(repoId, cert)
|
||||
|
||||
assertEquals(1, repoDao.getRepositories().size)
|
||||
assertEquals(cert, repoDao.getRepositories()[0].certificate)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import org.fdroid.database.TestUtils.assertRepoEquals
|
||||
import org.fdroid.index.v2.AntiFeatureV2
|
||||
import org.fdroid.index.v2.CategoryV2
|
||||
import org.fdroid.index.v2.ReleaseChannelV2
|
||||
import org.fdroid.index.v2.RepoV2
|
||||
import org.fdroid.test.DiffUtils.applyDiff
|
||||
import org.fdroid.test.DiffUtils.randomDiff
|
||||
import org.fdroid.test.TestRepoUtils.getRandomFileV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomLocalizedTextV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomMirror
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
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
|
||||
|
||||
/**
|
||||
* Tests that repository diffs get applied to the database correctly.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class RepositoryDiffTest : DbTest() {
|
||||
|
||||
private val j = 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])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun timestampDiffTwoReposInDb() {
|
||||
// insert repo
|
||||
val repo = getRandomRepo()
|
||||
repoDao.insertOrReplace(repo)
|
||||
|
||||
// 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
|
||||
|
||||
val updateTimestamp = Random.nextLong()
|
||||
val json = """
|
||||
{
|
||||
"timestamp": $updateTimestamp
|
||||
}""".trimIndent()
|
||||
|
||||
// decode diff from JSON and update DB with it
|
||||
val diff = j.parseToJsonElement(json).jsonObject // Json.decodeFromString<RepoDiffV2>(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])
|
||||
}
|
||||
|
||||
@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])
|
||||
}
|
||||
}
|
||||
|
||||
@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])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun antiFeaturesDiff() {
|
||||
val repo = getRandomRepo().copy(antiFeatures = getRandomMap {
|
||||
getRandomString() to AntiFeatureV2(
|
||||
icon = getRandomFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
})
|
||||
val antiFeatures = repo.antiFeatures.randomDiff {
|
||||
AntiFeatureV2(getRandomFileV2(), getRandomLocalizedTextV2(), 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])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun antiFeatureKeyChangeDiff() {
|
||||
val antiFeatureKey = getRandomString()
|
||||
val antiFeature = AntiFeatureV2(
|
||||
icon = getRandomFileV2(),
|
||||
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 = null,
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
))
|
||||
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])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun categoriesDiff() {
|
||||
val repo = getRandomRepo().copy(categories = getRandomMap {
|
||||
getRandomString() to CategoryV2(
|
||||
icon = getRandomFileV2(),
|
||||
name = getRandomLocalizedTextV2(),
|
||||
description = getRandomLocalizedTextV2(),
|
||||
)
|
||||
})
|
||||
val categories = repo.categories.randomDiff {
|
||||
CategoryV2(getRandomFileV2(), getRandomLocalizedTextV2(), getRandomLocalizedTextV2())
|
||||
}
|
||||
val json = """
|
||||
{
|
||||
"categories": ${Json.encodeToString(categories)}
|
||||
}""".trimIndent()
|
||||
testDiff(repo, json) { repos ->
|
||||
val expectedFeatures = repo.categories.applyDiff(categories)
|
||||
val expectedRepoCategories =
|
||||
expectedFeatures.toRepoCategories(repos[0].repoId)
|
||||
assertEquals(expectedRepoCategories.toSet(), repos[0].categories.toSet())
|
||||
assertRepoEquals(repo.copy(categories = expectedFeatures), repos[0])
|
||||
}
|
||||
}
|
||||
|
||||
@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])
|
||||
}
|
||||
}
|
||||
|
||||
private fun testDiff(repo: RepoV2, json: String, repoChecker: (List<Repository>) -> 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
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
120
libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt
Normal file
120
libs/database/src/dbTest/java/org/fdroid/database/TestUtils.kt
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
|
||||
internal object TestUtils {
|
||||
|
||||
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.map { it.toMirror(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).toSet()
|
||||
assertEquals(expectedCategories, repo.categories.toSet())
|
||||
// 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,
|
||||
)
|
||||
|
||||
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(),
|
||||
)
|
||||
|
||||
fun <T> LiveData<T>.getOrAwaitValue(): T? {
|
||||
val data = arrayOfNulls<Any>(1)
|
||||
val latch = CountDownLatch(1)
|
||||
val observer: Observer<T> = object : Observer<T> {
|
||||
override fun onChanged(o: T?) {
|
||||
data[0] = o
|
||||
latch.countDown()
|
||||
removeObserver(this)
|
||||
}
|
||||
}
|
||||
observeForever(observer)
|
||||
latch.await(2, TimeUnit.SECONDS)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return data[0] as T?
|
||||
}
|
||||
|
||||
fun <T> LiveData<T>.getOrFail(): T {
|
||||
return getOrAwaitValue() ?: fail()
|
||||
}
|
||||
}
|
||||
234
libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt
Normal file
234
libs/database/src/dbTest/java/org/fdroid/database/VersionTest.kt
Normal file
@@ -0,0 +1,234 @@
|
||||
package org.fdroid.database
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.index.v2.PackageVersionV2
|
||||
import org.fdroid.test.TestAppUtils.getRandomMetadataV2
|
||||
import org.fdroid.test.TestRepoUtils.getRandomRepo
|
||||
import org.fdroid.test.TestUtils.getRandomString
|
||||
import org.fdroid.test.TestVersionUtils.getRandomPackageVersionV2
|
||||
import org.junit.Rule
|
||||
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() {
|
||||
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun insertGetDeleteSingleVersion() {
|
||||
val repoId = repoDao.insertOrReplace(getRandomRepo())
|
||||
appDao.insert(repoId, packageName, getRandomMetadataV2())
|
||||
versionDao.insert(repoId, packageName, versionId1, packageVersion1, isCompatible1)
|
||||
|
||||
val appVersions = versionDao.getAppVersions(repoId, packageName)
|
||||
assertEquals(1, appVersions.size)
|
||||
val appVersion = appVersions[0]
|
||||
assertEquals(versionId1, appVersion.version.versionId)
|
||||
assertEquals(getVersion1(repoId), appVersion.version)
|
||||
val manifest = packageVersion1.manifest
|
||||
assertEquals(manifest.usesPermission.toSet(), appVersion.usesPermission.toSet())
|
||||
assertEquals(manifest.usesPermissionSdk23.toSet(), appVersion.usesPermissionSdk23.toSet())
|
||||
assertEquals(
|
||||
manifest.features.map { it.name }.toSet(),
|
||||
appVersion.version.manifest.features?.toSet()
|
||||
)
|
||||
|
||||
val versionedStrings = versionDao.getVersionedStrings(repoId, packageName)
|
||||
val expectedSize = manifest.usesPermission.size + manifest.usesPermissionSdk23.size
|
||||
assertEquals(expectedSize, versionedStrings.size)
|
||||
|
||||
versionDao.deleteAppVersion(repoId, packageName, versionId1)
|
||||
assertEquals(0, versionDao.getAppVersions(repoId, packageName).size)
|
||||
assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// get app versions from DB and assign them correctly
|
||||
val appVersions = versionDao.getAppVersions(packageName).getOrFail()
|
||||
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]
|
||||
|
||||
// check first version matches
|
||||
assertEquals(getVersion1(repoId), appVersion.version)
|
||||
val manifest = packageVersion1.manifest
|
||||
assertEquals(manifest.usesPermission.toSet(), appVersion.usesPermission.toSet())
|
||||
assertEquals(manifest.usesPermissionSdk23.toSet(), appVersion.usesPermissionSdk23.toSet())
|
||||
assertEquals(
|
||||
manifest.features.map { it.name }.toSet(),
|
||||
appVersion.version.manifest.features?.toSet()
|
||||
)
|
||||
|
||||
// check second version matches
|
||||
assertEquals(getVersion2(repoId), appVersion2.version)
|
||||
val manifest2 = packageVersion2.manifest
|
||||
assertEquals(manifest2.usesPermission.toSet(), appVersion2.usesPermission.toSet())
|
||||
assertEquals(manifest2.usesPermissionSdk23.toSet(),
|
||||
appVersion2.usesPermissionSdk23.toSet())
|
||||
assertEquals(
|
||||
manifest.features.map { it.name }.toSet(),
|
||||
appVersion.version.manifest.features?.toSet()
|
||||
)
|
||||
|
||||
// delete app and check that all associated data also gets deleted
|
||||
appDao.deleteAppMetadata(repoId, packageName)
|
||||
assertEquals(0, versionDao.getAppVersions(repoId, packageName).size)
|
||||
assertEquals(0, versionDao.getVersionedStrings(repoId, packageName).size)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// 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)
|
||||
|
||||
// disable second repo
|
||||
repoDao.setRepositoryEnabled(repoId2, false)
|
||||
|
||||
// now only two versions get returned
|
||||
assertEquals(2, versionDao.getAppVersions(packageName).getOrFail().size)
|
||||
assertEquals(2, 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))
|
||||
assertEquals(3, versions1.size)
|
||||
assertEquals(3, versions2.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)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// default app prefs don't change result
|
||||
var appPrefs = AppPrefs(packageName)
|
||||
appPrefsDao.update(appPrefs)
|
||||
assertEquals(1, 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)
|
||||
|
||||
// ignoring exact version code does change result
|
||||
appPrefs = appPrefs.toggleIgnoreVersionCodeUpdate(versionCode)
|
||||
appPrefsDao.update(appPrefs)
|
||||
assertEquals(0, 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)
|
||||
|
||||
// ignoring all updates does change result
|
||||
appPrefs = appPrefs.toggleIgnoreAllUpdates()
|
||||
appPrefsDao.update(appPrefs)
|
||||
assertEquals(0, 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)
|
||||
}
|
||||
|
||||
@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)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package org.fdroid.index.v1
|
||||
|
||||
import android.Manifest
|
||||
import android.net.Uri
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import org.fdroid.CompatibilityChecker
|
||||
import org.fdroid.database.DbTest
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.TestUtils.getOrAwaitValue
|
||||
import org.fdroid.database.TestUtils.getOrFail
|
||||
import org.fdroid.download.Downloader
|
||||
import org.fdroid.download.DownloaderFactory
|
||||
import org.fdroid.index.IndexUpdateResult
|
||||
import org.fdroid.index.SigningException
|
||||
import org.fdroid.index.TempFileProvider
|
||||
import org.fdroid.index.v2.ANTI_FEATURE_KNOWN_VULNERABILITY
|
||||
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
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1Processing() {
|
||||
val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
|
||||
val repo = repoDao.getRepository(repoId) ?: fail()
|
||||
downloadIndex(repo, TESTY_JAR)
|
||||
val result = indexUpdater.updateNewRepo(repo, TESTY_FINGERPRINT).noError()
|
||||
assertIs<IndexUpdateResult.Processed>(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<IndexUpdateResult.Unchanged>(result2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithWrongCert() {
|
||||
val repoId = repoDao.insertEmptyRepo(TESTY_CANONICAL_URL)
|
||||
val repo = repoDao.getRepository(repoId) ?: fail()
|
||||
downloadIndex(repo, TESTY_JAR)
|
||||
val result = indexUpdater.updateNewRepo(repo, "not the right fingerprint")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
assertIs<SigningException>(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.updateNewRepo(futureRepo, TESTY_FINGERPRINT)
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
assertIs<OldIndexException>(result.e)
|
||||
assertFalse((result.e as OldIndexException).isSameTimestamp)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithCorruptAppPackageName() {
|
||||
val result = testBadTestyJar("testy.at.or.at_corrupt_app_package_name_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithCorruptPackageName() {
|
||||
val result = testBadTestyJar("testy.at.or.at_corrupt_package_name_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithBadTestyJarNoManifest() {
|
||||
val result = testBadTestyJar("testy.at.or.at_no-MANIFEST.MF_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
assertIs<SigningException>(result.e)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithBadTestyJarNoSigningCert() {
|
||||
val result = testBadTestyJar("testy.at.or.at_no-.RSA_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithBadTestyJarNoSignature() {
|
||||
val result = testBadTestyJar("testy.at.or.at_no-.SF_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIndexV1WithBadTestyJarNoSignatureFiles() {
|
||||
val result = testBadTestyJar("testy.at.or.at_no-signature_index-v1.jar")
|
||||
assertIs<IndexUpdateResult.Error>(result)
|
||||
assertIs<SigningException>(result.e)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun downloadIndex(repo: Repository, jar: String) {
|
||||
val uri = Uri.parse("${repo.address}/index-v1.jar")
|
||||
|
||||
val jarFile = tmpFolder.newFile()
|
||||
assets.open(jar).use { inputStream ->
|
||||
jarFile.outputStream().use { inputStream.copyTo(it) }
|
||||
}
|
||||
every { tempFileProvider.createTempFile() } returns jarFile
|
||||
every {
|
||||
downloaderFactory.createWithTryFirstMirror(repo, uri, 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.updateNewRepo(repo, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" +
|
||||
"050030213110300e060355040b1307462d44726f6964310d300b060355040313" +
|
||||
"04736f7661301e170d3136303832333133333131365a170d3434303130393133" +
|
||||
"333131365a30213110300e060355040b1307462d44726f6964310d300b060355" +
|
||||
"04031304736f766130820222300d06092a864886f70d01010105000382020f00" +
|
||||
"3082020a0282020100dfdcd120f3ab224999dddf4ea33ea588d295e4d7130bef" +
|
||||
"48c143e9d76e5c0e0e9e5d45e64208e35feebc79a83f08939dd6a343b7d1e217" +
|
||||
"9930a105a1249ccd36d88ff3feffc6e4dc53dae0163a7876dd45ecc1ddb0adf5" +
|
||||
"099aa56c1a84b52affcd45d0711ffa4de864f35ac0333ebe61ea8673eeda35a8" +
|
||||
"8f6af678cc4d0f80b089338ac8f2a8279a64195c611d19445cab3fd1a020afed" +
|
||||
"9bd739bb95142fb2c00a8f847db5ef3325c814f8eb741bacf86ed3907bfe6e45" +
|
||||
"64d2de5895df0c263824e0b75407589bae2d3a4666c13b92102d8781a8ee9bb4" +
|
||||
"a5a1a78c4a9c21efdaf5584da42e84418b28f5a81d0456a3dc5b420991801e6b" +
|
||||
"21e38c99bbe018a5b2d690894a114bc860d35601416aa4dc52216aff8a288d47" +
|
||||
"75cddf8b72d45fd2f87303a8e9c0d67e442530be28eaf139894337266e0b33d5" +
|
||||
"7f949256ab32083bcc545bc18a83c9ab8247c12aea037e2b68dee31c734cb1f0" +
|
||||
"4f241d3b94caa3a2b258ffaf8e6eae9fbbe029a934dc0a0859c5f12033481269" +
|
||||
"3a1c09352340a39f2a678dbc1afa2a978bfee43afefcb7e224a58af2f3d647e5" +
|
||||
"745db59061236b8af6fcfd93b3602f9e456978534f3a7851e800071bf56da804" +
|
||||
"01c81d91c45f82568373af0576b1cc5eef9b85654124b6319770be3cdba3fbeb" +
|
||||
"e3715e8918fb6c8966624f3d0e815effac3d2ee06dd34ab9c693218b2c7c06ba" +
|
||||
"99d6b74d4f17b8c3cb0203010001a321301f301d0603551d0e04160414d62bee" +
|
||||
"9f3798509546acc62eb1de14b08b954d4f300d06092a864886f70d01010b0500" +
|
||||
"0382020100743f7c5692085895f9d1fffad390fb4202c15f123ed094df259185" +
|
||||
"960fd6dadf66cb19851070f180297bba4e6996a4434616573b375cfee94fee73" +
|
||||
"a4505a7ec29136b7e6c22e6436290e3686fe4379d4e3140ec6a08e70cfd3ed5b" +
|
||||
"634a5eb5136efaaabf5f38e0432d3d79568a556970b8cfba2972f5d23a3856d8" +
|
||||
"a981b9e9bbbbb88f35e708bde9cbc5f681cbd974085b9da28911296fe2579fa6" +
|
||||
"4bbe9fa0b93475a7a8db051080b0c5fade0d1c018e7858cd4cbe95145b0620e2" +
|
||||
"f632cbe0f8af9cbf22e2fdaa72245ae31b0877b07181cc69dd2df74454251d8d" +
|
||||
"e58d25e76354abe7eb690f22e59b08795a8f2c98c578e0599503d90859276340" +
|
||||
"72c82c9f82abd50fd12b8fd1a9d1954eb5cc0b4cfb5796b5aaec0356643b4a65" +
|
||||
"a368442d92ef94edd3ac6a2b7fe3571b8cf9f462729228aab023ef9183f73792" +
|
||||
"f5379633ccac51079177d604c6bc1873ada6f07d8da6d68c897e88a5fa5d63fd" +
|
||||
"b8df820f46090e0716e7562dd3c140ba279a65b996f60addb0abe29d4bf2f5ab" +
|
||||
"e89480771d492307b926d91f02f341b2148502903c43d40f3c6c86a811d06071" +
|
||||
"1f0698b384acdcc0add44eb54e42962d3d041accc715afd49407715adc09350c" +
|
||||
"b55e8d9281a3b0b6b5fcd91726eede9b7c8b13afdebb2c2b377629595f1096ba" +
|
||||
"62fb14946dbac5f3c5f0b4e5b712e7acc7dcf6c46cdc5e6d6dfdeee55a0c92c2" +
|
||||
"d70f080ac6"
|
||||
@@ -0,0 +1,294 @@
|
||||
package org.fdroid.index.v2
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import org.fdroid.CompatibilityChecker
|
||||
import org.fdroid.database.DbTest
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.TestUtils.assertTimestampRecent
|
||||
import org.fdroid.download.Downloader
|
||||
import org.fdroid.download.DownloaderFactory
|
||||
import org.fdroid.index.IndexFormatVersion.TWO
|
||||
import org.fdroid.index.IndexUpdateResult
|
||||
import org.fdroid.index.SigningException
|
||||
import org.fdroid.index.TempFileProvider
|
||||
import org.fdroid.test.TestDataEntryV2
|
||||
import org.fdroid.test.TestDataMaxV2
|
||||
import org.fdroid.test.TestDataMidV2
|
||||
import org.fdroid.test.TestDataMinV2
|
||||
import org.fdroid.test.VerifierConstants.CERTIFICATE
|
||||
import org.fdroid.test.VerifierConstants.FINGERPRINT
|
||||
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.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.test.fail
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class IndexV2UpdaterTest : DbTest() {
|
||||
|
||||
@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
|
||||
|
||||
@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")
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-min/entry.jar",
|
||||
jsonPath = "index-min-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.emptyToMin.index
|
||||
)
|
||||
val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError()
|
||||
assertEquals(IndexUpdateResult.Processed, result)
|
||||
assertDbEquals(repoId, TestDataMinV2.index)
|
||||
|
||||
// check that certificate and format version got entered
|
||||
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")
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-mid/entry.jar",
|
||||
jsonPath = "index-mid-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.emptyToMid.index
|
||||
)
|
||||
val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).noError()
|
||||
assertEquals(IndexUpdateResult.Processed, result)
|
||||
assertDbEquals(repoId, TestDataMidV2.index)
|
||||
assertTimestampRecent(repoDao.getRepository(repoId)?.lastUpdated)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFullIndexEmptyToMax() {
|
||||
val repoId = repoDao.insertEmptyRepo("http://example.org")
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-max/entry.jar",
|
||||
jsonPath = "index-max-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.emptyToMax.index
|
||||
)
|
||||
val result = indexUpdater.updateNewRepo(repo, FINGERPRINT).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/entry.jar",
|
||||
jsonPath = "diff-empty-mid/42.json",
|
||||
entryFileV2 = TestDataEntryV2.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")
|
||||
repoDao.updateRepository(repoId, CERTIFICATE)
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-min/entry.jar",
|
||||
jsonPath = "diff-empty-min/23.json",
|
||||
entryFileV2 = TestDataEntryV2.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")
|
||||
repoDao.updateRepository(repoId, CERTIFICATE)
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-max/entry.jar",
|
||||
jsonPath = "diff-empty-max/1337.json",
|
||||
entryFileV2 = TestDataEntryV2.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")
|
||||
repoDao.updateRepository(repoId, CERTIFICATE)
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-min/entry.jar",
|
||||
jsonPath = "diff-empty-min/23.json",
|
||||
entryFileV2 = TestDataEntryV2.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")
|
||||
repoDao.updateRepository(repoId, CERTIFICATE)
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-min/entry.jar",
|
||||
jsonPath = "diff-empty-min/23.json",
|
||||
entryFileV2 = TestDataEntryV2.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")
|
||||
repoDao.updateRepository(repoId, CERTIFICATE)
|
||||
// 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/entry.jar",
|
||||
jsonPath = "index-min-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.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/entry.jar",
|
||||
jsonPath = "index-min-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.emptyToMin.index
|
||||
)
|
||||
val result = indexUpdater.updateNewRepo(repo, "wrong fingerprint")
|
||||
assertTrue(result is IndexUpdateResult.Error)
|
||||
assertTrue(result.e is SigningException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNormalUpdateOnRepoWithMissingFingerprint() {
|
||||
val repoId = repoDao.insertEmptyRepo("http://example.org")
|
||||
val repo = prepareUpdate(
|
||||
repoId = repoId,
|
||||
entryPath = "diff-empty-min/entry.jar",
|
||||
jsonPath = "index-min-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.emptyToMin.index
|
||||
)
|
||||
val result = indexUpdater.update(repo)
|
||||
assertTrue(result is IndexUpdateResult.Error)
|
||||
assertTrue(result.e is IllegalArgumentException)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/entry.jar",
|
||||
jsonPath = "index-mid-v2.json",
|
||||
entryFileV2 = TestDataEntryV2.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)
|
||||
}
|
||||
|
||||
private fun prepareUpdate(
|
||||
repoId: Long,
|
||||
entryPath: String,
|
||||
jsonPath: String,
|
||||
entryFileV2: EntryFileV2,
|
||||
): Repository {
|
||||
val entryFile = tmpFolder.newFile()
|
||||
val indexFile = tmpFolder.newFile()
|
||||
val repo = repoDao.getRepository(repoId) ?: fail()
|
||||
val entryUri = Uri.parse("${repo.address}/entry.jar")
|
||||
val indexUri = Uri.parse("${repo.address}/${entryFileV2.name.trimStart('/')}")
|
||||
|
||||
assets.open(entryPath).use { inputStream ->
|
||||
entryFile.outputStream().use { inputStream.copyTo(it) }
|
||||
}
|
||||
assets.open(jsonPath).use { inputStream ->
|
||||
indexFile.outputStream().use { inputStream.copyTo(it) }
|
||||
}
|
||||
|
||||
every { tempFileProvider.createTempFile() } returnsMany listOf(entryFile, indexFile)
|
||||
every {
|
||||
downloaderFactory.createWithTryFirstMirror(repo, entryUri, entryFile)
|
||||
} returns downloader
|
||||
every { downloader.download(-1) } just Runs
|
||||
every {
|
||||
downloaderFactory.createWithTryFirstMirror(repo, indexUri, indexFile)
|
||||
} returns downloader
|
||||
every { downloader.download(entryFileV2.size, entryFileV2.sha256) } 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
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user