diff --git a/app/build.gradle b/app/build.gradle index c72a56209e3d66e39fe6ad77d7f558a47fc3d886..0b9421d182568e90d017d81d4716a1646500641f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -137,6 +137,7 @@ android { includeAndroidResources = true all { systemProperty 'robolectric.usePreinstrumentedJars', 'false' + maxParallelForks = 1 } } } diff --git a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorkManager.kt b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorkManager.kt index 3b44ca8ff9d11d0e7b5c1c225e2862ee43ab1ec7..fc3a177c8503494d95021cb80f6e878daf71c800 100644 --- a/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorkManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/updates/UpdatesWorkManager.kt @@ -18,6 +18,8 @@ package foundation.e.apps.data.install.updates import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.work.Constraints import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy @@ -45,25 +47,26 @@ object UpdatesWorkManager { } private fun buildOneTimeWorkRequest(): OneTimeWorkRequest { + val spec = buildOneTimeRequestSpec() return OneTimeWorkRequest.Builder(UpdatesWorker::class.java).apply { - setConstraints(WorkRequestConstraints.build(WorkType.UpdateOneTime)) - setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) - addTag(TAG_WORK_USER_INITIATED_UPDATE) - }.setInputData( - Data.Builder() - .putBoolean(UpdatesWorker.IS_AUTO_UPDATE, false) - .build() - ).build() + setConstraints(spec.constraints) + if (spec.expedited) { + setExpedited(spec.outOfQuotaPolicy) + } + spec.tags.forEach { addTag(it) } + setInputData(spec.inputData) + }.build() } private fun buildPeriodicWorkRequest(interval: Long): PeriodicWorkRequest { + val spec = buildPeriodicRequestSpec(interval) return PeriodicWorkRequest.Builder( UpdatesWorker::class.java, - interval, + spec.intervalHours, TimeUnit.HOURS ).apply { - setConstraints(WorkRequestConstraints.build(WorkType.UpdatePeriodic)) - addTag(TAG_WORK_PERIODIC_UPDATE) + setConstraints(spec.constraints) + spec.tags.forEach { addTag(it) } }.build() } @@ -80,4 +83,42 @@ object UpdatesWorkManager { ) Timber.i("UpdatesWorker started") } + + @VisibleForTesting + internal data class OneTimeRequestSpec( + val inputData: Data, + val constraints: Constraints, + val tags: Set, + val expedited: Boolean, + val outOfQuotaPolicy: OutOfQuotaPolicy + ) + + @VisibleForTesting + internal data class PeriodicRequestSpec( + val intervalHours: Long, + val constraints: Constraints, + val tags: Set + ) + + @VisibleForTesting + internal fun buildOneTimeRequestSpec(): OneTimeRequestSpec { + return OneTimeRequestSpec( + inputData = Data.Builder() + .putBoolean(UpdatesWorker.IS_AUTO_UPDATE, false) + .build(), + constraints = WorkRequestConstraints.build(WorkType.UpdateOneTime), + tags = setOf(TAG_WORK_USER_INITIATED_UPDATE), + expedited = true, + outOfQuotaPolicy = OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST + ) + } + + @VisibleForTesting + internal fun buildPeriodicRequestSpec(interval: Long): PeriodicRequestSpec { + return PeriodicRequestSpec( + intervalHours = interval, + constraints = WorkRequestConstraints.build(WorkType.UpdatePeriodic), + tags = setOf(TAG_WORK_PERIODIC_UPDATE) + ) + } } diff --git a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt index 27bccf90b35c64240a581a4416fa689efb7c3fd9..b3944e20c2048578933e2620dd996fa3d37c36a7 100644 --- a/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt +++ b/app/src/main/java/foundation/e/apps/data/install/workmanager/InstallWorkManager.kt @@ -1,6 +1,8 @@ package foundation.e.apps.data.install.workmanager import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.work.Constraints import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder @@ -14,17 +16,16 @@ object InstallWorkManager { const val INSTALL_WORK_NAME = "APP_LOUNGE_INSTALL_APP" fun enqueueWork(context: Context, appInstall: AppInstall, isUpdateWork: Boolean = false): Operation { - val inputData = Data.Builder() - .putString(InstallAppWorker.INPUT_DATA_FUSED_DOWNLOAD, appInstall.id) - .putBoolean(InstallAppWorker.IS_UPDATE_WORK, isUpdateWork) - .build() - + val spec = buildInstallRequestSpec(appInstall, isUpdateWork) val request = OneTimeWorkRequestBuilder() - .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) - .setInputData(inputData) - .addTag(appInstall.id) - .addTag(INSTALL_WORK_NAME) - .setConstraints(WorkRequestConstraints.build(WorkType.InstallOneTime)) + .setInputData(spec.inputData) + .setConstraints(spec.constraints) + .apply { + if (spec.expedited) { + setExpedited(spec.outOfQuotaPolicy) + } + spec.tags.forEach { addTag(it) } + } .build() val operation = WorkManager.getInstance(context) @@ -52,4 +53,30 @@ object InstallWorkManager { } fun getUniqueWorkName(appPackageName: String): String = "${INSTALL_WORK_NAME}/$appPackageName" + + @VisibleForTesting + internal data class InstallRequestSpec( + val inputData: Data, + val constraints: Constraints, + val tags: Set, + val expedited: Boolean, + val outOfQuotaPolicy: OutOfQuotaPolicy + ) + + @VisibleForTesting + internal fun buildInstallRequestSpec( + appInstall: AppInstall, + isUpdateWork: Boolean + ): InstallRequestSpec { + return InstallRequestSpec( + inputData = Data.Builder() + .putString(InstallAppWorker.INPUT_DATA_FUSED_DOWNLOAD, appInstall.id) + .putBoolean(InstallAppWorker.IS_UPDATE_WORK, isUpdateWork) + .build(), + constraints = WorkRequestConstraints.build(WorkType.InstallOneTime), + tags = setOf(appInstall.id, INSTALL_WORK_NAME), + expedited = true, + outOfQuotaPolicy = OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST + ) + } } diff --git a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt index 065ef7a3219cd5d6620159be418593fa66da928b..909819eac32d19a0e2ca8627b6503ad9a111dc91 100644 --- a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkManagerTest.kt @@ -90,7 +90,7 @@ class UpdatesWorkManagerTest { @Test fun enqueueWork_buildsExpectedPeriodicRequest() { - UpdatesWorkManager.enqueueWork(context, interval = 6, ExistingPeriodicWorkPolicy.REPLACE) + UpdatesWorkManager.enqueueWork(context, interval = 6, ExistingPeriodicWorkPolicy.KEEP) val workInfo = getActiveUniqueWorkInfo("updates_work") val workSpec = getWorkSpec(workInfo.id) @@ -105,21 +105,22 @@ class UpdatesWorkManagerTest { fun enqueueWork_respectsExistingPeriodicWorkPolicy() { UpdatesWorkManager.enqueueWork(context, interval = 6, ExistingPeriodicWorkPolicy.KEEP) val initialWorkInfo = getActiveUniqueWorkInfo("updates_work") - val initialWorkSpec = getWorkSpec(initialWorkInfo.id) + val initialSpec = getWorkSpec(initialWorkInfo.id) + assertThat(initialSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(6)) UpdatesWorkManager.enqueueWork(context, interval = 12, ExistingPeriodicWorkPolicy.KEEP) val keptWorkInfo = getActiveUniqueWorkInfo("updates_work") - val keptWorkSpec = getWorkSpec(keptWorkInfo.id) + val keptSpec = getWorkSpec(keptWorkInfo.id) assertThat(keptWorkInfo.id).isEqualTo(initialWorkInfo.id) - assertThat(keptWorkSpec.intervalDuration).isEqualTo(initialWorkSpec.intervalDuration) + assertThat(keptSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(6)) UpdatesWorkManager.enqueueWork(context, interval = 24, ExistingPeriodicWorkPolicy.REPLACE) val replacedWorkInfo = getActiveUniqueWorkInfo("updates_work") - val replacedWorkSpec = getWorkSpec(replacedWorkInfo.id) + val replacedSpec = getWorkSpec(replacedWorkInfo.id) assertThat(replacedWorkInfo.id).isNotEqualTo(initialWorkInfo.id) - assertThat(replacedWorkSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(24)) + assertThat(replacedSpec.intervalDuration).isEqualTo(TimeUnit.HOURS.toMillis(24)) } private fun getActiveUniqueWorkInfo(uniqueWorkName: String): WorkInfo { diff --git a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt index 4376c7dfafc58f88c517cb9c888e7389c2f471dd..933ef0aacf202f943a1e229554a1a1e9fcfb82bc 100644 --- a/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/updates/UpdatesWorkerTest.kt @@ -296,8 +296,12 @@ class UpdatesWorkerTest { systemAppsUpdatesRepository ) - val response = worker.checkManualUpdateRunning() - assertFalse(response) + try { + val response = worker.checkManualUpdateRunning() + assertFalse(response) + } finally { + WorkManager.getInstance(appContext).cancelAllWork().result.get() + } } @Test @@ -398,13 +402,17 @@ class UpdatesWorkerTest { systemAppsUpdatesRepository ) - val result = worker.doWork() + try { + val result = worker.doWork() - assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.success()) - verify(blockedAppRepository, never()).fetchUpdateOfAppWarningList() - verify(systemAppsUpdatesRepository, never()).fetchUpdatableSystemApps(true) - verify(updatesManagerRepository, never()).getUpdates() - verify(updatesManagerRepository, never()).getUpdatesOSS() + assertThat(result).isEqualTo(androidx.work.ListenableWorker.Result.success()) + verify(blockedAppRepository, never()).fetchUpdateOfAppWarningList() + verify(systemAppsUpdatesRepository, never()).fetchUpdatableSystemApps(true) + verify(updatesManagerRepository, never()).getUpdates() + verify(updatesManagerRepository, never()).getUpdatesOSS() + } finally { + WorkManager.getInstance(appContext).cancelAllWork().result.get() + } } @Test diff --git a/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt b/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt index 422ca6804894889e4344534a5ad9eb627e815168..490d343c866d34a3f3d605e3693e9b6e3a7ec256 100644 --- a/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt +++ b/app/src/test/java/foundation/e/apps/data/install/workmanager/InstallWorkManagerTest.kt @@ -24,6 +24,7 @@ import android.os.Build import androidx.test.core.app.ApplicationProvider import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder +import androidx.work.OutOfQuotaPolicy import androidx.work.WorkInfo import androidx.work.WorkManager import androidx.work.Worker @@ -67,10 +68,8 @@ class InstallWorkManagerTest { fun enqueueWork_buildsExpectedRequestForInstall() { val appInstall = AppInstall(id = "id.install", packageName = "foundation.e.install") - InstallWorkManager.enqueueWork(context, appInstall).result.get() - - val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) - val workInfo = workManager.getWorkInfosForUniqueWork(uniqueWorkName).get().single() + InstallWorkManager.enqueueWork(context, appInstall, isUpdateWork = false).result.get() + val workInfo = getActiveUniqueWorkInfo(InstallWorkManager.getUniqueWorkName(appInstall.packageName)) val workSpec = getWorkSpec(workInfo.id) assertThat(workInfo.tags).containsAtLeast(appInstall.id, InstallWorkManager.INSTALL_WORK_NAME) @@ -79,6 +78,9 @@ class InstallWorkManagerTest { assertThat(workSpec.input.getBoolean(InstallAppWorker.IS_UPDATE_WORK, true)).isFalse() assertThat(workSpec.constraints.requiredNetworkType).isEqualTo(NetworkType.CONNECTED) assertThat(workSpec.constraints.requiresStorageNotLow()).isTrue() + assertThat(workSpec.expedited).isTrue() + assertThat(workSpec.outOfQuotaPolicy) + .isEqualTo(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) } @Test @@ -86,9 +88,7 @@ class InstallWorkManagerTest { val appInstall = AppInstall(id = "id.update", packageName = "foundation.e.update") InstallWorkManager.enqueueWork(context, appInstall, isUpdateWork = true).result.get() - - val uniqueWorkName = InstallWorkManager.getUniqueWorkName(appInstall.packageName) - val workInfo = workManager.getWorkInfosForUniqueWork(uniqueWorkName).get().single() + val workInfo = getActiveUniqueWorkInfo(InstallWorkManager.getUniqueWorkName(appInstall.packageName)) val workSpec = getWorkSpec(workInfo.id) assertThat(workSpec.input.getBoolean(InstallAppWorker.IS_UPDATE_WORK, false)).isTrue() @@ -161,11 +161,6 @@ class InstallWorkManagerTest { .isEqualTo("${InstallWorkManager.INSTALL_WORK_NAME}/") } - private fun getWorkSpec(workId: UUID): WorkSpec { - val workManagerImpl = WorkManagerImpl.getInstance(context) - return requireNotNull(workManagerImpl.workDatabase.workSpecDao().getWorkSpec(workId.toString())) - } - private fun waitUntilFinished(workId: UUID): WorkInfo.State { repeat(30) { val state = requireNotNull(workManager.getWorkInfoById(workId).get()).state @@ -177,6 +172,16 @@ class InstallWorkManagerTest { return requireNotNull(workManager.getWorkInfoById(workId).get()).state } + private fun getActiveUniqueWorkInfo(uniqueWorkName: String): WorkInfo { + return workManager.getWorkInfosForUniqueWork(uniqueWorkName).get() + .single { !it.state.isFinished } + } + + private fun getWorkSpec(workId: UUID): WorkSpec { + val workManagerImpl = WorkManagerImpl.getInstance(context) + return requireNotNull(workManagerImpl.workDatabase.workSpecDao().getWorkSpec(workId.toString())) + } + class NoOpWorker( appContext: Context, workerParams: WorkerParameters diff --git a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt index be2d274d23517cb4997d09c96c695d6421675464..90d0f26957693d5fc98af04fa31e350b23ad4949 100644 --- a/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt +++ b/app/src/test/java/foundation/e/apps/data/login/playstore/OauthAuthDataBuilderTest.kt @@ -1,5 +1,6 @@ package foundation.e.apps.data.login.playstore +import android.os.Build import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.helpers.AuthHelper import com.google.common.truth.Truth.assertThat @@ -13,8 +14,13 @@ import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config import java.util.Properties +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [Build.VERSION_CODES.R, Build.VERSION_CODES.TIRAMISU]) class OauthAuthDataBuilderTest { @Before diff --git a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt index a453e3ed4477f378982f49839ffcf0726810408e..8ede690ac722e2a3663977bb0aeda3ae350348c8 100644 --- a/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt +++ b/app/src/test/java/foundation/e/apps/install/workmanager/InstallOrchestratorTest.kt @@ -89,6 +89,7 @@ class InstallOrchestratorTest { unmockkObject(InstallWorkManager) isInstallWorkManagerMocked = false } + WorkManager.getInstance(context).cancelAllWork().result.get() } @Test diff --git a/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt b/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt index 6a6f79aa859fdd2c5cd923a322fe7b08d61644ec..3d2befd2bbb4d92a64cd9df8b0f1150d93519e3f 100644 --- a/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt +++ b/app/src/test/java/foundation/e/apps/ui/search/v2/SearchViewModelV2Test.kt @@ -21,6 +21,7 @@ package foundation.e.apps.ui.search.v2 import androidx.paging.AsyncPagingDataDiffer import androidx.paging.PagingData import androidx.recyclerview.widget.ListUpdateCallback +import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.App import foundation.e.apps.data.Stores import foundation.e.apps.data.application.data.Application @@ -55,6 +56,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.launch +import kotlinx.coroutines.cancel import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -62,6 +64,7 @@ import org.junit.Assert.assertFalse import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before +import org.junit.After import org.junit.Rule import org.junit.Test @@ -134,6 +137,13 @@ class SearchViewModelV2Test { buildViewModel() } + @After + fun tearDown() { + if (::viewModel.isInitialized) { + viewModel.viewModelScope.cancel() + } + } + private fun buildStores(): Stores { val playStoreRepository = mockk(relaxed = true) val cleanApkAppsRepository = mockk(relaxed = true)