Loading app/src/test/java/foundation/e/apps/data/install/AppManagerImplTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -404,7 +404,7 @@ class AppManagerImplTest { } @Test fun updateDownloadStatus_installingDeletesSelfUpdateEntryBeforeInstall() = runTest { fun updateDownloadStatus_installingKeepsSelfUpdateEntryForDelayedCallbacks() = runTest { val appInstall = createAppInstall(id = "151", packageName = context.packageName).apply { status = Status.DOWNLOADED downloadIdMap = mutableMapOf(101L to true) Loading @@ -414,7 +414,7 @@ class AppManagerImplTest { appManager.updateDownloadStatus(appInstall, Status.INSTALLING) assertThat(appInstallDAO.getDownloadById(appInstall.id)).isNull() assertThat(appInstallDAO.getDownloadById(appInstall.id)?.status).isEqualTo(Status.INSTALLING) } private fun initTest(hasAnyExistingWork: Boolean = false): AppInstall { Loading app/src/test/java/foundation/e/apps/data/install/download/DownloadManagerUtilsTest.kt +38 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,44 @@ class DownloadManagerUtilsTest { coVerify { appManagerWrapper.getFusedDownload(99L, any()) } coVerify(exactly = 0) { appManagerWrapper.updateAppInstall(any()) } coVerify(exactly = 0) { appManagerWrapper.finalizeUnattendedFailure(any()) } coVerify(exactly = 0) { appManagerWrapper.reportInstallationIssue(any()) } coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any(), any()) } } @OptIn(DelicateCoroutinesApi::class) @Test fun updateDownloadStatus_finalizesFailedDownloadThroughCanonicalFinalizer() = runTest { val context = mockk<Context>(relaxed = true) val appManagerWrapper = mockk<AppManager>(relaxed = true) val downloadManager = mockk<DownloadManager>(relaxed = true) val storageNotificationManager = mockk<StorageNotificationManager>(relaxed = true) val appInstall = AppInstall( id = "download-id", name = "Test App", packageName = "com.example.app", source = InstallationSource.PLAY_STORE, status = Status.DOWNLOADING, downloadURLList = mutableListOf("https://example.org/main.apk"), downloadIdMap = mutableMapOf(1L to false) ) val utils = DownloadManagerUtils( context, appManagerWrapper, downloadManager, storageNotificationManager, backgroundScope ) coEvery { appManagerWrapper.getFusedDownload(1L, any()) } returns appInstall every { downloadManager.hasDownloadFailed(1L) } returns true every { downloadManager.getDownloadFailureReason(1L) } returns PlatformDownloadManager.ERROR_UNKNOWN utils.updateDownloadStatus(1L) advanceTimeBy(1500) runCurrent() coVerify { appManagerWrapper.finalizeUnattendedFailure(appInstall) } coVerify(exactly = 0) { appManagerWrapper.reportInstallationIssue(any()) } coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any(), any()) } } Loading app/src/test/java/foundation/e/apps/data/install/pkg/InstallerServiceTest.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.data.install.pkg import android.content.Intent import android.content.pm.PackageInstaller import androidx.test.core.app.ApplicationProvider import foundation.e.apps.data.application.AppManager import foundation.e.apps.data.faultyApps.FaultyAppRepository import foundation.e.apps.data.installation.model.AppInstall import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class InstallerServiceTest { @Test fun onStartCommand_finalizesResolvedInstallFailure() = runTest { val service = InstallerService().apply { appManager = mockk(relaxed = true) appLoungePackageManager = mockk(relaxed = true) faultyAppRepository = mockk(relaxed = true) coroutineScope = this@runTest } val appInstall = AppInstall(id = "app-id", packageName = "org.signal") coEvery { service.appManager.getFusedDownload(packageName = "org.signal") } returns appInstall val intent = Intent(ApplicationProvider.getApplicationContext(), InstallerService::class.java) .putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) .putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, "org.signal") service.onStartCommand(intent, 0, 0) advanceUntilIdle() coVerify { service.appManager.finalizeUnattendedFailure(appInstall) } } } app/src/test/java/foundation/e/apps/data/install/pkg/PkgManagerBRTest.kt 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.data.install.pkg import android.content.Intent import android.net.Uri import androidx.test.core.app.ApplicationProvider import foundation.e.apps.data.application.AppManager import foundation.e.apps.data.faultyApps.FaultyAppRepository import foundation.e.apps.data.installation.model.AppInstall import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) @Config(sdk = [android.os.Build.VERSION_CODES.R]) class PkgManagerBRTest { @Test fun onReceive_finalizesResolvedBroadcastInstallFailure() = runTest { val appManager = mockk<AppManager>(relaxed = true) val appInstall = AppInstall(id = "app-id", packageName = "org.signal") coEvery { appManager.getFusedDownload(packageName = "org.signal") } returns appInstall val receiver = PkgManagerBR( appManager = appManager, appLoungePackageManager = mockk(relaxed = true), faultyAppRepository = mockk<FaultyAppRepository>(relaxed = true), coroutineScope = this, ) val intent = Intent(AppLoungePackageManager.ERROR_PACKAGE_INSTALL) .setData(Uri.parse("package:org.signal")) receiver.onReceive(ApplicationProvider.getApplicationContext(), intent) advanceUntilIdle() coVerify { appManager.finalizeUnattendedFailure(appInstall) } } } app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt +26 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,32 @@ class UpdatesWorkManagerTest { assertThat(replacedWorkSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(24)) } @Test fun startUpdateAllWork_canceledUserWorkIsNotLeftActive() { UpdatesWorkManager.startUpdateAllWork(context) val workInfo = getActiveUniqueWorkInfo("updates_work_user") workManager.cancelWorkById(workInfo.id).result.get() val activeWorkInfos = workManager.getWorkInfosByTag( UpdatesWorkManager.TAG_WORK_USER_INITIATED_UPDATE ).get().filter { !it.state.isFinished } assertThat(activeWorkInfos).isEmpty() } @Test fun enqueueWork_canceledPeriodicWorkIsNotLeftActive() { UpdatesWorkManager.enqueueWork(context, interval = 6, ExistingPeriodicWorkPolicy.REPLACE) val workInfo = getActiveUniqueWorkInfo("updates_work") workManager.cancelWorkById(workInfo.id).result.get() val activeWorkInfos = workManager.getWorkInfosByTag( UpdatesWorkManager.TAG_WORK_PERIODIC_UPDATE ).get().filter { !it.state.isFinished } assertThat(activeWorkInfos).isEmpty() } private fun getActiveUniqueWorkInfo(uniqueWorkName: String): WorkInfo { return workManager.getWorkInfosForUniqueWork(uniqueWorkName).get() .single { !it.state.isFinished } Loading Loading
app/src/test/java/foundation/e/apps/data/install/AppManagerImplTest.kt +2 −2 Original line number Diff line number Diff line Loading @@ -404,7 +404,7 @@ class AppManagerImplTest { } @Test fun updateDownloadStatus_installingDeletesSelfUpdateEntryBeforeInstall() = runTest { fun updateDownloadStatus_installingKeepsSelfUpdateEntryForDelayedCallbacks() = runTest { val appInstall = createAppInstall(id = "151", packageName = context.packageName).apply { status = Status.DOWNLOADED downloadIdMap = mutableMapOf(101L to true) Loading @@ -414,7 +414,7 @@ class AppManagerImplTest { appManager.updateDownloadStatus(appInstall, Status.INSTALLING) assertThat(appInstallDAO.getDownloadById(appInstall.id)).isNull() assertThat(appInstallDAO.getDownloadById(appInstall.id)?.status).isEqualTo(Status.INSTALLING) } private fun initTest(hasAnyExistingWork: Boolean = false): AppInstall { Loading
app/src/test/java/foundation/e/apps/data/install/download/DownloadManagerUtilsTest.kt +38 −0 Original line number Diff line number Diff line Loading @@ -122,6 +122,44 @@ class DownloadManagerUtilsTest { coVerify { appManagerWrapper.getFusedDownload(99L, any()) } coVerify(exactly = 0) { appManagerWrapper.updateAppInstall(any()) } coVerify(exactly = 0) { appManagerWrapper.finalizeUnattendedFailure(any()) } coVerify(exactly = 0) { appManagerWrapper.reportInstallationIssue(any()) } coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any(), any()) } } @OptIn(DelicateCoroutinesApi::class) @Test fun updateDownloadStatus_finalizesFailedDownloadThroughCanonicalFinalizer() = runTest { val context = mockk<Context>(relaxed = true) val appManagerWrapper = mockk<AppManager>(relaxed = true) val downloadManager = mockk<DownloadManager>(relaxed = true) val storageNotificationManager = mockk<StorageNotificationManager>(relaxed = true) val appInstall = AppInstall( id = "download-id", name = "Test App", packageName = "com.example.app", source = InstallationSource.PLAY_STORE, status = Status.DOWNLOADING, downloadURLList = mutableListOf("https://example.org/main.apk"), downloadIdMap = mutableMapOf(1L to false) ) val utils = DownloadManagerUtils( context, appManagerWrapper, downloadManager, storageNotificationManager, backgroundScope ) coEvery { appManagerWrapper.getFusedDownload(1L, any()) } returns appInstall every { downloadManager.hasDownloadFailed(1L) } returns true every { downloadManager.getDownloadFailureReason(1L) } returns PlatformDownloadManager.ERROR_UNKNOWN utils.updateDownloadStatus(1L) advanceTimeBy(1500) runCurrent() coVerify { appManagerWrapper.finalizeUnattendedFailure(appInstall) } coVerify(exactly = 0) { appManagerWrapper.reportInstallationIssue(any()) } coVerify(exactly = 0) { appManagerWrapper.cancelDownload(any(), any()) } } Loading
app/src/test/java/foundation/e/apps/data/install/pkg/InstallerServiceTest.kt 0 → 100644 +59 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.data.install.pkg import android.content.Intent import android.content.pm.PackageInstaller import androidx.test.core.app.ApplicationProvider import foundation.e.apps.data.application.AppManager import foundation.e.apps.data.faultyApps.FaultyAppRepository import foundation.e.apps.data.installation.model.AppInstall import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) class InstallerServiceTest { @Test fun onStartCommand_finalizesResolvedInstallFailure() = runTest { val service = InstallerService().apply { appManager = mockk(relaxed = true) appLoungePackageManager = mockk(relaxed = true) faultyAppRepository = mockk(relaxed = true) coroutineScope = this@runTest } val appInstall = AppInstall(id = "app-id", packageName = "org.signal") coEvery { service.appManager.getFusedDownload(packageName = "org.signal") } returns appInstall val intent = Intent(ApplicationProvider.getApplicationContext(), InstallerService::class.java) .putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE) .putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, "org.signal") service.onStartCommand(intent, 0, 0) advanceUntilIdle() coVerify { service.appManager.finalizeUnattendedFailure(appInstall) } } }
app/src/test/java/foundation/e/apps/data/install/pkg/PkgManagerBRTest.kt 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ package foundation.e.apps.data.install.pkg import android.content.Intent import android.net.Uri import androidx.test.core.app.ApplicationProvider import foundation.e.apps.data.application.AppManager import foundation.e.apps.data.faultyApps.FaultyAppRepository import foundation.e.apps.data.installation.model.AppInstall import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config @OptIn(ExperimentalCoroutinesApi::class) @RunWith(RobolectricTestRunner::class) @Config(sdk = [android.os.Build.VERSION_CODES.R]) class PkgManagerBRTest { @Test fun onReceive_finalizesResolvedBroadcastInstallFailure() = runTest { val appManager = mockk<AppManager>(relaxed = true) val appInstall = AppInstall(id = "app-id", packageName = "org.signal") coEvery { appManager.getFusedDownload(packageName = "org.signal") } returns appInstall val receiver = PkgManagerBR( appManager = appManager, appLoungePackageManager = mockk(relaxed = true), faultyAppRepository = mockk<FaultyAppRepository>(relaxed = true), coroutineScope = this, ) val intent = Intent(AppLoungePackageManager.ERROR_PACKAGE_INSTALL) .setData(Uri.parse("package:org.signal")) receiver.onReceive(ApplicationProvider.getApplicationContext(), intent) advanceUntilIdle() coVerify { appManager.finalizeUnattendedFailure(appInstall) } } }
app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt +26 −0 Original line number Diff line number Diff line Loading @@ -123,6 +123,32 @@ class UpdatesWorkManagerTest { assertThat(replacedWorkSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(24)) } @Test fun startUpdateAllWork_canceledUserWorkIsNotLeftActive() { UpdatesWorkManager.startUpdateAllWork(context) val workInfo = getActiveUniqueWorkInfo("updates_work_user") workManager.cancelWorkById(workInfo.id).result.get() val activeWorkInfos = workManager.getWorkInfosByTag( UpdatesWorkManager.TAG_WORK_USER_INITIATED_UPDATE ).get().filter { !it.state.isFinished } assertThat(activeWorkInfos).isEmpty() } @Test fun enqueueWork_canceledPeriodicWorkIsNotLeftActive() { UpdatesWorkManager.enqueueWork(context, interval = 6, ExistingPeriodicWorkPolicy.REPLACE) val workInfo = getActiveUniqueWorkInfo("updates_work") workManager.cancelWorkById(workInfo.id).result.get() val activeWorkInfos = workManager.getWorkInfosByTag( UpdatesWorkManager.TAG_WORK_PERIODIC_UPDATE ).get().filter { !it.state.isFinished } assertThat(activeWorkInfos).isEmpty() } private fun getActiveUniqueWorkInfo(uniqueWorkName: String): WorkInfo { return workManager.getWorkInfosForUniqueWork(uniqueWorkName).get() .single { !it.state.isFinished } Loading