mirror of
https://github.com/f-droid/fdroidclient.git
synced 2026-08-07 04:53:37 -04:00
Add AppUpdateManagerTest
This commit is contained in:
6 files changed
+310
-7
No files matched your search
@@ -226,8 +226,10 @@ dependencies {
|
||||
testImplementation libs.androidx.test.core
|
||||
testImplementation libs.junit
|
||||
testImplementation libs.robolectric
|
||||
testImplementation libs.mockk
|
||||
testImplementation libs.mockito.core
|
||||
testImplementation libs.hamcrest
|
||||
testImplementation libs.slf4j.simple
|
||||
|
||||
androidTestImplementation libs.androidx.test.core
|
||||
androidTestImplementation libs.androidx.core.testing
|
||||
|
||||
+5
-1
@@ -19,7 +19,11 @@
|
||||
-keep class junit.** { *; }
|
||||
-dontwarn junit.**
|
||||
|
||||
-keep class kotlin.reflect.** { *; }
|
||||
-keep class io.mockk.** { *; }
|
||||
-keep class kotlin.io.** { *; }
|
||||
-keep class kotlin.collections.** { *; }
|
||||
-keep class java.util.concurrent.Executor { *; }
|
||||
|
||||
-keep class androidx.arch.core.executor.ArchTaskExecutor {*;}
|
||||
|
||||
@@ -28,4 +32,4 @@
|
||||
}
|
||||
|
||||
# This is necessary so that RemoteWorkManager can be initialized (also marked with @Keep)
|
||||
-keep class androidx.work.WorkManager { *; }
|
||||
-keep class androidx.work.** { *; }
|
||||
@@ -2,29 +2,31 @@ package org.fdroid.fdroid
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import mu.KotlinLogging
|
||||
import org.acra.util.versionCodeLong
|
||||
import org.fdroid.database.DbUpdateChecker
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.UpdatableApp
|
||||
import org.fdroid.download.DownloaderFactory
|
||||
import org.fdroid.fdroid.AppUpdateStatusManager.Status.Downloading
|
||||
import org.fdroid.fdroid.data.Apk
|
||||
import org.fdroid.fdroid.data.App
|
||||
import org.fdroid.fdroid.installer.ApkCache
|
||||
import org.fdroid.fdroid.installer.InstallManagerService
|
||||
import org.fdroid.fdroid.installer.InstallerFactory
|
||||
import org.fdroid.fdroid.net.DownloaderFactory
|
||||
import org.fdroid.index.RepoManager
|
||||
|
||||
class AppUpdateManager(
|
||||
class AppUpdateManager @JvmOverloads constructor(
|
||||
private val context: Context,
|
||||
private val repoManager: RepoManager,
|
||||
private val updateChecker: DbUpdateChecker,
|
||||
private val downloaderFactory: DownloaderFactory =
|
||||
org.fdroid.fdroid.net.DownloaderFactory.INSTANCE,
|
||||
private val statusManager: AppUpdateStatusManager = AppUpdateStatusManager.getInstance(context),
|
||||
) {
|
||||
|
||||
private val log = KotlinLogging.logger { }
|
||||
private val downloaderFactory = DownloaderFactory.INSTANCE
|
||||
private val statusManager = AppUpdateStatusManager.getInstance(context)
|
||||
|
||||
fun updateApps() {
|
||||
// get apps with updates pending
|
||||
@@ -33,6 +35,7 @@ class AppUpdateManager(
|
||||
.sortedWith { app1, app2 ->
|
||||
// our own app will be last to update
|
||||
if (app1.packageName == context.packageName) return@sortedWith 1
|
||||
if (app2.packageName == context.packageName) return@sortedWith -1
|
||||
// other apps are sorted by name
|
||||
(app1.name ?: "").compareTo(app2.name ?: "", ignoreCase = true)
|
||||
}
|
||||
@@ -46,7 +49,7 @@ class AppUpdateManager(
|
||||
private val installManagerService = InstallManagerService.getInstance(context)
|
||||
private val legacyApp = App(app)
|
||||
private val legacyApk = Apk(app.update, repo)
|
||||
private val uri = Uri.parse(legacyApk.canonicalUrl)
|
||||
private val uri = legacyApk.canonicalUrl.toUri()
|
||||
private var lastProgress = 0L
|
||||
|
||||
override fun onInstallProcessStarted() {
|
||||
@@ -80,7 +83,7 @@ class AppUpdateManager(
|
||||
// legacy cruft
|
||||
val legacyApp = App(app)
|
||||
val legacyApk = Apk(app.update, repo)
|
||||
val uri = Uri.parse(legacyApk.canonicalUrl)
|
||||
val uri = legacyApk.canonicalUrl.toUri()
|
||||
// check if app was already installed in the meantime
|
||||
try {
|
||||
val packageInfo = context.packageManager.getPackageInfo(app.packageName, 0)
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
package org.fdroid.fdroid
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import io.mockk.Runs
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
import org.fdroid.database.AppManifest
|
||||
import org.fdroid.database.AppVersion
|
||||
import org.fdroid.database.DbUpdateChecker
|
||||
import org.fdroid.database.Repository
|
||||
import org.fdroid.database.UpdatableApp
|
||||
import org.fdroid.download.Downloader
|
||||
import org.fdroid.fdroid.data.App
|
||||
import org.fdroid.fdroid.installer.InstallManagerService
|
||||
import org.fdroid.fdroid.installer.Installer
|
||||
import org.fdroid.fdroid.installer.InstallerFactory
|
||||
import org.fdroid.index.RepoManager
|
||||
import org.fdroid.index.v2.FileV1
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class AppUpdateManagerTest {
|
||||
|
||||
private val context: Context = mockk()
|
||||
private val repoManager: RepoManager = mockk()
|
||||
private val updateChecker: DbUpdateChecker = mockk()
|
||||
private val preferences: Preferences = mockk()
|
||||
private val downloaderFactory: org.fdroid.download.DownloaderFactory = mockk()
|
||||
private val statusManager: AppUpdateStatusManager = mockk()
|
||||
|
||||
private val appUpdateManager =
|
||||
AppUpdateManager(context, repoManager, updateChecker, downloaderFactory, statusManager)
|
||||
|
||||
private val downloader: Downloader = mockk()
|
||||
private val installer: Installer = mockk()
|
||||
private val packageManager: PackageManager = mockk()
|
||||
private val installManagerService: InstallManagerService = mockk(relaxed = true)
|
||||
private val repoUri: Uri = mockk(relaxed = true)
|
||||
|
||||
private val app1 = App().apply {
|
||||
packageName = "org.example.1"
|
||||
name = "One"
|
||||
repoId = 42
|
||||
}
|
||||
private val app2 = App().apply {
|
||||
packageName = "org.example.2"
|
||||
name = "Two"
|
||||
repoId = 42
|
||||
}
|
||||
private val repo: Repository = mockk(relaxed = true) {
|
||||
every { repoId } returns app1.repoId
|
||||
every { address } returns "https://example.org/repo"
|
||||
}
|
||||
private val file1 = FileV1("one", "one")
|
||||
private val file2 = FileV1("two", "one")
|
||||
private val version1: AppVersion = mockk<AppVersion>(relaxed = true) {
|
||||
every { repoId } returns app1.repoId
|
||||
every { packageName } returns app1.packageName
|
||||
every { manifest } returns AppManifest("1", 1)
|
||||
every { file } returns file1
|
||||
}
|
||||
private val version2: AppVersion = mockk<AppVersion>(relaxed = true) {
|
||||
every { repoId } returns app2.repoId
|
||||
every { packageName } returns app2.packageName
|
||||
every { manifest } returns AppManifest("2", 2)
|
||||
every { file } returns file2
|
||||
}
|
||||
|
||||
// need to mock apps as their constructor is internal
|
||||
private val updatableApp1: UpdatableApp = mockk(relaxed = true) {
|
||||
every { packageName } returns app1.packageName
|
||||
every { name } returns app1.name
|
||||
every { summary } returns app1.summary
|
||||
every { repoId } returns app1.repoId
|
||||
every { update } returns version1
|
||||
every { update.file } returns file1
|
||||
every { update.manifest } returns AppManifest("1", 1)
|
||||
every { update.repoId } returns app1.repoId
|
||||
every { update.packageName } returns app1.packageName
|
||||
every { installedVersionCode } returns app1.installedVersionCode
|
||||
}
|
||||
private val updatableApp2: UpdatableApp = mockk(relaxed = true) {
|
||||
every { packageName } returns app2.packageName
|
||||
every { name } returns app2.name
|
||||
every { summary } returns app2.summary
|
||||
every { repoId } returns app2.repoId
|
||||
every { update } returns version2
|
||||
every { update.repoId } returns app2.repoId
|
||||
every { update.packageName } returns app2.packageName
|
||||
every { update.file } returns file2
|
||||
every { update.manifest } returns AppManifest("2", 2)
|
||||
every { installedVersionCode } returns app2.installedVersionCode
|
||||
}
|
||||
|
||||
init {
|
||||
mockkStatic(Preferences::get)
|
||||
every { Preferences.get() } returns preferences
|
||||
|
||||
mockkStatic(InstallManagerService::getInstance)
|
||||
every { InstallManagerService.getInstance(any()) } returns installManagerService
|
||||
|
||||
mockkStatic(Uri::parse)
|
||||
every { Uri.parse(any()) } returns repoUri
|
||||
|
||||
mockkStatic(InstallerFactory::create)
|
||||
every { InstallerFactory.create(any(), any(), any()) } returns installer
|
||||
|
||||
every { context.packageManager } returns packageManager
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNoUpdates() {
|
||||
val updates = emptyList<UpdatableApp>()
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
|
||||
appUpdateManager.updateApps()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSomeUpdates() {
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloaderFactory.create(repo, any(), file2, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } just Runs
|
||||
every { installer.installPackage(any(), any()) } just Runs
|
||||
|
||||
appUpdateManager.updateApps()
|
||||
|
||||
verify(exactly = 2) {
|
||||
installManagerService.onDownloadComplete(any())
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersionAlreadyInstalled() {
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(app1.packageName, any<Int>())
|
||||
} returns getPackageInfo(1)
|
||||
every {
|
||||
packageManager.getPackageInfo(app2.packageName, any<Int>())
|
||||
} returns getPackageInfo(2)
|
||||
|
||||
appUpdateManager.updateApps()
|
||||
|
||||
verify(exactly = 0) {
|
||||
installManagerService.onDownloadComplete(any())
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOurOwnAppIsUpdatedLast() {
|
||||
val installer1: Installer = mockk()
|
||||
val installer2: Installer = mockk()
|
||||
val updates = listOf(updatableApp1, updatableApp2)
|
||||
val updatesSorted = listOf(updatableApp2, updatableApp1)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns app1.packageName // we are app1
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { repoManager.getRepository(app2.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updatesSorted, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloaderFactory.create(repo, any(), file2, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } just Runs
|
||||
every {
|
||||
InstallerFactory.create(any(), match { it.packageName == app1.packageName }, any())
|
||||
} returns installer1
|
||||
every {
|
||||
InstallerFactory.create(any(), match { it.packageName == app2.packageName }, any())
|
||||
} returns installer2
|
||||
every { installer1.installPackage(any(), any()) } just Runs
|
||||
every { installer2.installPackage(any(), any()) } just Runs
|
||||
|
||||
appUpdateManager.updateApps()
|
||||
|
||||
verifyOrder {
|
||||
// app1 gets installed last, because it is us and updating us kills us
|
||||
installer2.installPackage(any(), any())
|
||||
installer1.installPackage(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFailedDownloadSkipsUpdate() {
|
||||
val updates = listOf(updatableApp1)
|
||||
|
||||
every { preferences.backendReleaseChannels } returns null
|
||||
every {
|
||||
updateChecker.getUpdatableApps(
|
||||
releaseChannels = null,
|
||||
onlyFromPreferredRepo = true,
|
||||
includeKnownVulnerabilities = false,
|
||||
)
|
||||
} returns updates
|
||||
every { context.packageName } returns null
|
||||
every { repoManager.getRepository(app1.repoId) } returns repo
|
||||
every { statusManager.addUpdatableApps(updates, false) } just Runs
|
||||
every { statusManager.addApk(any(), any(), any(), any()) } just Runs
|
||||
every {
|
||||
packageManager.getPackageInfo(any<String>(), any<Int>())
|
||||
} returns getPackageInfo(0)
|
||||
every { context.cacheDir } returns File("/tmp/fdroid-app-update-test")
|
||||
every { downloaderFactory.create(repo, any(), file1, any()) } returns downloader
|
||||
every { downloader.setListener(any()) } just Runs
|
||||
every { downloader.download() } throws IOException("foo bar")
|
||||
|
||||
appUpdateManager.updateApps()
|
||||
|
||||
verify(exactly = 0) {
|
||||
installer.installPackage(any(), any())
|
||||
}
|
||||
verify {
|
||||
installManagerService.onDownloadFailed(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPackageInfo(versionCode: Int) = PackageInfo().apply {
|
||||
@Suppress("DEPRECATION") // longVersionCode doesn't work
|
||||
this.versionCode = versionCode
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user