Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 60151358 authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

test(install): cover unattended failure flows

parent 6eb9b8ef
Loading
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -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)
@@ -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 {
+38 −0
Original line number Diff line number Diff line
@@ -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()) }
    }
+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) }
    }
}
+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) }
    }
}
+26 −0
Original line number Diff line number Diff line
@@ -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