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

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

test: add AppInstallProcessor update scheduling coverage

Protect the split between update scheduling in AppInstallProcessor and regular install scheduling in InstallOrchestrator by verifying when WorkManager should and should not be enqueued.
parent 904dd715
Loading
Loading
Loading
Loading
+297 −10
Original line number Diff line number Diff line
@@ -23,10 +23,15 @@ import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.work.Operation
import com.google.common.util.concurrent.Futures
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.application.ApplicationRepository
import foundation.e.apps.data.enums.ResultStatus
import foundation.e.apps.data.application.data.Application
import foundation.e.apps.data.enums.Source
import foundation.e.apps.data.enums.Status
import foundation.e.apps.data.enums.Type
import foundation.e.apps.data.fdroid.FDroidRepository
import foundation.e.apps.data.install.AppInstallComponents
import foundation.e.apps.data.install.AppInstallRepository
@@ -36,13 +41,16 @@ import foundation.e.apps.data.install.models.AppInstall
import foundation.e.apps.data.install.notification.StorageNotificationManager
import foundation.e.apps.data.install.workmanager.AppInstallProcessor
import foundation.e.apps.data.preference.PlayStoreAuthStore
import foundation.e.apps.data.install.workmanager.InstallWorkManager
import foundation.e.apps.data.system.StorageComputer
import foundation.e.apps.domain.ValidateAppAgeLimitUseCase
import foundation.e.apps.domain.model.ContentRatingValidity
import foundation.e.apps.domain.preferences.SessionRepository
import foundation.e.apps.util.MainCoroutineRule
import io.mockk.every
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.verify
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.unmockkObject
@@ -50,6 +58,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -101,6 +110,8 @@ class AppInstallProcessorTest {
    @Mock
    private lateinit var storageNotificationManager: StorageNotificationManager

    private var isInstallWorkManagerMocked = false

    @Before
    fun setup() {
        MockitoAnnotations.openMocks(this)
@@ -122,6 +133,14 @@ class AppInstallProcessorTest {
        )
    }

    @After
    fun teardown() {
        if (isInstallWorkManagerMocked) {
            unmockkObject(InstallWorkManager)
            isInstallWorkManagerMocked = false
        }
    }

    @Test
    fun processInstallTest() = runTest {
        val fusedDownload = initTest()
@@ -227,7 +246,7 @@ class AppInstallProcessorTest {
    @Test
    fun canEnqueue_returnsTrueWhenAllChecksPass() = runTest {
        val appInstall = AppInstall(
            type = foundation.e.apps.data.enums.Type.PWA,
            type = Type.PWA,
            id = "123",
            status = Status.AWAITING,
            downloadURLList = mutableListOf("apk"),
@@ -243,7 +262,7 @@ class AppInstallProcessorTest {
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            everyNetworkAvailable()
            io.mockk.every { StorageComputer.spaceMissing(appInstall) } returns 0
            every { StorageComputer.spaceMissing(appInstall) } returns 0

            val result = processor.canEnqueue(appInstall)

@@ -257,7 +276,7 @@ class AppInstallProcessorTest {
    @Test
    fun canEnqueue_returnsFalseWhenNetworkUnavailable() = runTest {
        val appInstall = AppInstall(
            type = foundation.e.apps.data.enums.Type.PWA,
            type = Type.PWA,
            id = "123",
            status = Status.AWAITING,
            downloadURLList = mutableListOf("apk"),
@@ -273,7 +292,7 @@ class AppInstallProcessorTest {
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            everyNetworkUnavailable()
            io.mockk.every { StorageComputer.spaceMissing(appInstall) } returns 0
            every { StorageComputer.spaceMissing(appInstall) } returns 0

            val result = processor.canEnqueue(appInstall)

@@ -287,7 +306,7 @@ class AppInstallProcessorTest {
    @Test
    fun canEnqueue_returnsFalseWhenStorageMissing() = runTest {
        val appInstall = AppInstall(
            type = foundation.e.apps.data.enums.Type.PWA,
            type = Type.PWA,
            id = "123",
            status = Status.AWAITING,
            downloadURLList = mutableListOf("apk"),
@@ -303,7 +322,7 @@ class AppInstallProcessorTest {
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            everyNetworkAvailable()
            io.mockk.every { StorageComputer.spaceMissing(appInstall) } returns 100L
            every { StorageComputer.spaceMissing(appInstall) } returns 100L

            val result = processor.canEnqueue(appInstall)

@@ -318,7 +337,7 @@ class AppInstallProcessorTest {
    @Test
    fun canEnqueue_returnsFalseWhenAddDownloadFails() = runTest {
        val appInstall = AppInstall(
            type = foundation.e.apps.data.enums.Type.PWA,
            type = Type.PWA,
            id = "123",
            status = Status.AWAITING,
            downloadURLList = mutableListOf("apk"),
@@ -334,7 +353,7 @@ class AppInstallProcessorTest {
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            everyNetworkAvailable()
            io.mockk.every { StorageComputer.spaceMissing(appInstall) } returns 0L
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.canEnqueue(appInstall)

@@ -348,7 +367,7 @@ class AppInstallProcessorTest {
    @Test
    fun canEnqueue_returnsFalseWhenAgeLimitInvalid() = runTest {
        val appInstall = AppInstall(
            type = foundation.e.apps.data.enums.Type.PWA,
            type = Type.PWA,
            id = "123",
            status = Status.AWAITING,
            downloadURLList = mutableListOf("apk"),
@@ -364,7 +383,7 @@ class AppInstallProcessorTest {
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.UNKNOWN, ContentRatingValidity(false)))
            everyNetworkAvailable()
            io.mockk.every { StorageComputer.spaceMissing(appInstall) } returns 0L
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.canEnqueue(appInstall)

@@ -375,6 +394,210 @@ class AppInstallProcessorTest {
        }
    }

    @Test
    fun enqueueFusedDownload_returnsTrueAndEnqueuesWorkForUpdate() = runTest {
        val appInstall = createEnqueueAppInstall()
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerSuccess()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true)

            assertTrue(result)
            coVerify { appManagerWrapper.updateAwaiting(appInstall) }
            verify(exactly = 1) { InstallWorkManager.enqueueWork(appInstall, true) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun enqueueFusedDownload_returnsFalseAndMarksIssueWhenUpdateEnqueueFails() = runTest {
        val appInstall = createEnqueueAppInstall()
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerFailure()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = true, isSystemApp = true)

            assertEquals(false, result)
            coVerify { appManagerWrapper.updateAwaiting(appInstall) }
            coVerify { appManagerWrapper.installationIssue(appInstall) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun enqueueFusedDownload_returnsTrueWithoutEnqueueingWorkForRegularInstall() = runTest {
        val appInstall = createEnqueueAppInstall()
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerSuccess()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true)

            assertTrue(result)
            coVerify { appManagerWrapper.updateAwaiting(appInstall) }
            verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any()) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun enqueueFusedDownload_skipsWorkManagerFailurePathForRegularInstall() = runTest {
        val appInstall = createEnqueueAppInstall()
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerFailure()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.enqueueFusedDownload(appInstall, isAnUpdate = false, isSystemApp = true)

            assertTrue(result)
            verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any()) }
            coVerify(exactly = 0) { appManagerWrapper.installationIssue(appInstall) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun initAppInstall_enqueuesUpdateWorkWhenExplicitFlagIsTrue() = runTest {
        val application = createApplication(status = Status.INSTALLED)
        val appInstall = createExpectedAppInstall(application)
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerSuccess()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.initAppInstall(application, isAnUpdate = true)

            assertTrue(result)
            verify(exactly = 1) { InstallWorkManager.enqueueWork(appInstall, true) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun initAppInstall_enqueuesUpdateWorkWhenApplicationIsUpdatable() = runTest {
        val application = createApplication(status = Status.UPDATABLE)
        val appInstall = createExpectedAppInstall(application)
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerSuccess()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.initAppInstall(application, isAnUpdate = false)

            assertTrue(result)
            verify(exactly = 1) { InstallWorkManager.enqueueWork(appInstall, true) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun initAppInstall_enqueuesUpdateWorkWhenAppIsAlreadyInstalled() = runTest {
        val application = createApplication(status = Status.INSTALLED)
        val appInstall = createExpectedAppInstall(application)
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerSuccess()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns true
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.initAppInstall(application, isAnUpdate = false)

            assertTrue(result)
            verify(exactly = 1) { InstallWorkManager.enqueueWork(appInstall, true) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    @Test
    fun initAppInstall_doesNotEnqueueWorkWhenInstallIsNotAnUpdate() = runTest {
        val application = createApplication(status = Status.INSTALLED)
        val appInstall = createExpectedAppInstall(application)
        val appManagerWrapper = mockk<AppManagerWrapper>(relaxed = true)
        val processor = createProcessorForCanEnqueue(appManagerWrapper)

        mockInstallWorkManagerFailure()
        mockkObject(StorageComputer)
        try {
            everyNetworkAvailable()
            Mockito.`when`(validateAppAgeRatingUseCase(appInstall))
                .thenReturn(ResultSupreme.create(ResultStatus.OK, ContentRatingValidity(true)))
            every { appManagerWrapper.isFusedDownloadInstalled(appInstall) } returns false
            coEvery { appManagerWrapper.addDownload(appInstall) } returns true
            every { StorageComputer.spaceMissing(appInstall) } returns 0L

            val result = processor.initAppInstall(application, isAnUpdate = false)

            assertTrue(result)
            verify(exactly = 0) { InstallWorkManager.enqueueWork(any(), any()) }
        } finally {
            unmockkObject(StorageComputer)
        }
    }

    private suspend fun runProcessInstall(appInstall: AppInstall): AppInstall? {
        appInstallProcessor.processInstall(appInstall.id, false) {
            // _ignored_
@@ -408,6 +631,70 @@ class AppInstallProcessorTest {
        )
    }

    private fun createEnqueueAppInstall() = AppInstall(
        id = "123",
        status = Status.AWAITING,
        downloadURLList = mutableListOf("https://example.org/app.apk"),
        packageName = "com.example.app",
        type = Type.PWA,
        source = Source.PWA
    )

    private fun createApplication(status: Status) = Application(
        _id = "123",
        name = "Test app",
        package_name = "com.example.app",
        status = status,
        source = Source.PWA,
        type = Type.PWA,
        latest_version_code = 1L,
        isFree = true,
        isSystemApp = true,
        url = "https://example.org/app.apk"
    )

    private fun createExpectedAppInstall(application: Application) = AppInstall(
        application._id,
        application.source,
        application.status,
        application.name,
        application.package_name,
        mutableListOf(),
        mutableMapOf(),
        application.status,
        application.type,
        application.icon_image_path,
        application.latest_version_code,
        application.offer_type,
        application.isFree,
        application.originalSize
    ).also {
        it.contentRating = application.contentRating
        if (it.type == Type.PWA || application.source == Source.SYSTEM_APP) {
            it.downloadURLList = mutableListOf(application.url)
        }
    }

    private fun mockInstallWorkManagerSuccess() {
        mockkObject(InstallWorkManager)
        isInstallWorkManagerMocked = true
        every { InstallWorkManager.getUniqueWorkName(any()) } answers { callOriginal() }
        every { InstallWorkManager.enqueueWork(any(), any()) } returns successfulOperation()
    }

    private fun mockInstallWorkManagerFailure() {
        mockkObject(InstallWorkManager)
        isInstallWorkManagerMocked = true
        every { InstallWorkManager.getUniqueWorkName(any()) } answers { callOriginal() }
        every { InstallWorkManager.enqueueWork(any(), any()) } throws RuntimeException("enqueue failed")
    }

    private fun successfulOperation(): Operation {
        val operation = mock<Operation>()
        whenever(operation.result).thenReturn(Futures.immediateFuture(Operation.SUCCESS))
        return operation
    }

    private fun everyNetworkAvailable() {
        val connectivityManager = mock<ConnectivityManager>()
        val network = mock<Network>()