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

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

test(updates): add unit tests for snapshot safety, edge cases, and error handling

- UpdatesDao: verify snapshots remain stable during concurrent modifications
- DownloadProgressTracker: cover null/missing app scenarios
- InstallOrchestrator: test graceful handling of repository errors
- InstallationCompletionHandler: validate null input and non-installed status guards
parent ec0571a7
Loading
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -66,6 +66,41 @@ class UpdatesDaoTest {
        assertThat(snapshot).containsExactly(firstInstall, secondInstall).inOrder()
    }

    @Test
    fun appsAwaitingForUpdate_snapshotIsSafeToIterateWhileDaoIsModified() {
        val firstApp = Application(name = "App1", package_name = "app.one")
        val secondApp = Application(name = "App2", package_name = "app.two")
        val thirdApp = Application(name = "App3", package_name = "app.three")

        UpdatesDao.addItemsForUpdate(listOf(firstApp, secondApp))

        val snapshot = UpdatesDao.appsAwaitingForUpdate

        UpdatesDao.addItemsForUpdate(listOf(thirdApp))
        UpdatesDao.removeUpdateIfExists(firstApp.package_name)

        val items = snapshot.map { it.package_name }
        assertThat(items).containsExactly("app.one", "app.two").inOrder()
    }

    @Test
    fun successfulUpdatedApps_snapshotIsSafeToIterateWhileDaoIsModified() {
        val firstInstall = AppInstall(id = "1", packageName = "app.one")
        val secondInstall = AppInstall(id = "2", packageName = "app.two")
        val thirdInstall = AppInstall(id = "3", packageName = "app.three")

        UpdatesDao.addSuccessfullyUpdatedApp(firstInstall)
        UpdatesDao.addSuccessfullyUpdatedApp(secondInstall)

        val snapshot = UpdatesDao.successfulUpdatedApps

        UpdatesDao.addSuccessfullyUpdatedApp(thirdInstall)
        UpdatesDao.clearSuccessfullyUpdatedApps()

        val items = snapshot.map { it.packageName }
        assertThat(items).containsExactly("app.one", "app.two").inOrder()
    }

    private fun resetDao() {
        UpdatesDao.addItemsForUpdate(emptyList())
        UpdatesDao.clearSuccessfullyUpdatedApps()
+35 −0
Original line number Diff line number Diff line
@@ -21,6 +21,10 @@ import foundation.e.apps.data.application.AppManager
import foundation.e.apps.data.install.download.data.DownloadProgress
import foundation.e.apps.data.installation.model.AppInstall
import foundation.e.apps.domain.model.install.Status
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Type
import io.mockk.coEvery
import io.mockk.mockk
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
@@ -84,4 +88,35 @@ class DownloadProgressTrackerTest {

        assertEquals(100, percent)
    }

    @Test
    fun calculateProgress_appNotFound_returnsMinusOne() = runTest {
        val application = Application(
            _id = "missing-id",
            package_name = "com.missing.app",
            source = Source.PLAY_STORE,
            type = Type.NATIVE
        )
        coEvery { appManager.getDownloadList() } returns emptyList()
        val progress = DownloadProgress(
            totalSizeBytes = mutableMapOf(1L to 100L),
            bytesDownloadedSoFar = mutableMapOf(1L to 50L),
        )

        val result = downloadProgressTracker.calculateProgress(application, progress)

        assertEquals(-1, result)
    }

    @Test
    fun calculateProgress_nullApplication_returnsMinusOne() = runTest {
        val progress = DownloadProgress(
            totalSizeBytes = mutableMapOf(1L to 100L),
            bytesDownloadedSoFar = mutableMapOf(1L to 50L),
        )

        val result = downloadProgressTracker.calculateProgress(null, progress)

        assertEquals(-1, result)
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -409,6 +409,25 @@ class InstallOrchestratorTest {
        verifyMockito(appInstallRepository, times(1)).getDownloads()
    }

    @Test
    fun init_handlesReevaluationErrorGracefully() = runTest {
        val awaiting = createAppInstall(id = "app.awaiting.error", status = Status.AWAITING)
        mockInstallWorkManagerSuccess()

        whenever(appInstallRepository.getDownloads())
            .thenReturn(flowOf(emptyList()), flowOf(listOf(awaiting)))
        whenever(appInstallRepository.getDownloadList())
            .thenThrow(RuntimeException("repository failed"))
            .thenReturn(listOf(awaiting))

        val installOrchestrator = InstallOrchestrator(context, createOrchestratorScope(), appManager, appInstallRepository)

        installOrchestrator.init()
        advanceOrchestrator()

        verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any(), any()) }
    }

    @Test
    fun init_isIdempotent() = runTest {
        val awaiting = createAppInstall(id = "app.awaiting.once", status = Status.AWAITING)
+39 −0
Original line number Diff line number Diff line
@@ -163,6 +163,45 @@ class InstallationCompletionHandlerTest {
        verify { UpdatesNotifier.showNotification(context, "Update", "Updated message") }
    }

    @Test
    fun onInstallFinished_doesNothingWhenAppInstallIsNull() = runTest {
        handler.onInstallFinished(null, true)

        verify(exactly = 0) { appInstallationManager.getInstallationStatus(any()) }
        assertTrue(UpdatesDao.successfulUpdatedApps.isEmpty())
        verify(exactly = 0) { UpdatesNotifier.showNotification(any(), any<String>(), any<String>()) }
    }

    @Test
    fun onInstallFinished_doesNotTrackNonInstalledStatus() = runTest {
        val appInstall = AppInstall(id = "123", packageName = "com.example.app", isUpdateRequest = true)
        every { appInstallationManager.getInstallationStatus(appInstall) } returns Status.INSTALLATION_ISSUE

        handler.onInstallFinished(appInstall, true)

        assertTrue(UpdatesDao.successfulUpdatedApps.isEmpty())
        verify(exactly = 0) { UpdatesNotifier.showNotification(any(), any<String>(), any<String>()) }
    }

    @Test
    fun onInstallFinished_doesNotNotifyWhenNoSuccessfulUpdates() = runTest {
        val appInstall = AppInstall(id = "123", packageName = "com.example.app", isUpdateRequest = true)
        appInstallRepository.addDownload(
            AppInstall(
                id = "pending",
                status = Status.AWAITING,
                packageName = "com.example.pending",
                isUpdateRequest = true
            )
        )
        every { appInstallationManager.getInstallationStatus(appInstall) } returns Status.INSTALLED

        handler.onInstallFinished(appInstall, true)

        assertTrue(UpdatesDao.successfulUpdatedApps.contains(appInstall))
        verify(exactly = 0) { UpdatesNotifier.showNotification(any(), any<String>(), any<String>()) }
    }

    @Test
    fun onInstallFinished_clearsTrackedUpdatesAfterNotification() = runTest {
        val appInstall = AppInstall(id = "123", packageName = "com.example.app", isUpdateRequest = true)