diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 1abe7f4f77c695f19ad258bda96b2979add68f8e..06c5a2b041197bd7a1e9386f56bdfc9e43a3d329 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -73,7 +73,7 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Timber.d(">>> onCreate") + binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) diff --git a/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt index b1c8260094ab939ee7e655b2b52bea9d3fece903..479460fb70586921d4355fadb1198096fae3f03b 100644 --- a/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt +++ b/app/src/main/java/foundation/e/apps/api/faultyApps/FaultyAppRepository.kt @@ -20,11 +20,13 @@ package foundation.e.apps.api.faultyApps +import foundation.e.apps.OpenForTesting import foundation.e.apps.api.fused.data.FusedApp import javax.inject.Inject import javax.inject.Singleton @Singleton +@OpenForTesting class FaultyAppRepository @Inject constructor(private val faultyAppDao: FaultyAppDao) { suspend fun addFaultyApp(packageName: String, error: String) { diff --git a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt index c575c14647ef522e14208d392d6aa66fc32e24c5..5fac54cdf2a0555bf69293e199c624f805ff87ac 100644 --- a/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/application/ApplicationFragment.kt @@ -713,7 +713,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { return } val downloadedSize = "${ - Formatter.formatFileSize(requireContext(), progressResult.second).substringBefore(" MB") + Formatter.formatFileSize(requireContext(), progressResult.second).substringBefore(" MB") }/${Formatter.formatFileSize(requireContext(), progressResult.first)}" val progressPercentage = ((progressResult.second / progressResult.first.toDouble()) * 100f).toInt() diff --git a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt index 7c18659934ae3107e97c89b763cf3f5a9596e596..80273b8c8f8835ae51540c371e84075ac7b0440f 100644 --- a/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt +++ b/app/src/main/java/foundation/e/apps/updates/UpdatesFragment.kt @@ -63,6 +63,7 @@ import foundation.e.apps.utils.toast import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @AndroidEntryPoint @@ -90,21 +91,6 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte _binding = FragmentUpdatesBinding.bind(view) binding.button.isEnabled = false - - /* - * Explanation of double observers in HomeFragment.kt - */ - - /*mainActivityViewModel.internetConnection.observe(viewLifecycleOwner) { - if (!updatesViewModel.updatesList.value?.first.isNullOrEmpty()) { - return@observe - } - refreshDataOrRefreshToken(mainActivityViewModel) - } - mainActivityViewModel.authData.observe(viewLifecycleOwner) { - refreshDataOrRefreshToken(mainActivityViewModel) - }*/ - setupListening() authObjects.observe(viewLifecycleOwner) { @@ -139,48 +125,67 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte adapter = listAdapter layoutManager = LinearLayoutManager(view.context) } + observeAppInstallationWork() + observeUpdateList(listAdapter) + + viewLifecycleOwner.lifecycleScope.launch { + EventBus.events.flowWithLifecycle(viewLifecycleOwner.lifecycle) + .filter { appEvent -> appEvent is AppEvent.UpdateEvent }.collectLatest { + handleUpdateEvent(it) + } + } + } + private fun observeUpdateList(listAdapter: ApplicationListRVAdapter?) { updatesViewModel.updatesList.observe(viewLifecycleOwner) { listAdapter?.setData(it.first) if (!isDownloadObserverAdded) { + handleStateNoUpdates(it.first) observeDownloadList() isDownloadObserverAdded = true } + stopLoadingUI() - if (!it.first.isNullOrEmpty()) { - binding.button.isEnabled = true - binding.noUpdates.visibility = View.GONE - } else { - binding.noUpdates.visibility = View.VISIBLE - binding.button.isEnabled = false - } - WorkManager.getInstance(requireContext()) - .getWorkInfosForUniqueWorkLiveData(INSTALL_WORK_NAME) - .observe(viewLifecycleOwner) { workInfoList -> - lifecycleScope.launchWhenResumed { - binding.button.isEnabled = !( - it.first.isNullOrEmpty() || - updatesViewModel.checkWorkInfoListHasAnyUpdatableWork( - workInfoList - ) - ) - } - } + Timber.d("===>> observeupdate list called") + if (it.second != ResultStatus.OK) { + val exception = GPlayException(it.second == ResultStatus.TIMEOUT) + val alertDialogBuilder = AlertDialog.Builder(requireContext()) + onTimeout(exception, alertDialogBuilder) + } + } + } - /*if (it.second != ResultStatus.OK) { - onTimeout() - }*/ + private fun handleStateNoUpdates(list: List?) { + if (!list.isNullOrEmpty()) { + binding.button.isEnabled = true + binding.noUpdates.visibility = View.GONE + } else { + binding.noUpdates.visibility = View.VISIBLE + binding.button.isEnabled = false } + } - viewLifecycleOwner.lifecycleScope.launch { - EventBus.events.flowWithLifecycle(viewLifecycleOwner.lifecycle) - .filter { appEvent -> appEvent is AppEvent.UpdateEvent }.collectLatest { - handleUpdateEvent(it) + private fun observeAppInstallationWork() { + WorkManager.getInstance(requireContext()) + .getWorkInfosForUniqueWorkLiveData(INSTALL_WORK_NAME) + .observe(viewLifecycleOwner) { workInfoList -> + lifecycleScope.launchWhenResumed { + binding.button.isEnabled = shouldUpdateButtonEnable(workInfoList) } - } + } } + private fun shouldUpdateButtonEnable(workInfoList: MutableList) = + !updatesViewModel.updatesList.value?.first.isNullOrEmpty() && + ( + workInfoList.isNullOrEmpty() || + ( + !updatesViewModel.checkWorkInfoListHasAnyUpdatableWork(workInfoList) && + updatesViewModel.hasAnyUpdatableApp() + ) + ) + private fun handleUpdateEvent(appEvent: AppEvent) { val event = appEvent.data as ResultSupreme.WorkError<*> when (event.data) { @@ -213,9 +218,7 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte ApplicationDialogFragment( title = getString(R.string.dialog_title_paid_app, fusedApp.name), message = getString( - R.string.dialog_paidapp_message, - fusedApp.name, - fusedApp.price + R.string.dialog_paidapp_message, fusedApp.name, fusedApp.price ), positiveButtonText = getString(R.string.dialog_confirm), positiveButtonAction = { @@ -225,36 +228,6 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte ).show(childFragmentManager, "UpdatesFragment") } - /*override fun onTimeout() { - if (!isTimeoutDialogDisplayed()) { - stopLoadingUI() - displayTimeoutAlertDialog( - timeoutFragment = this, - activity = requireActivity(), - message = - if (updatesViewModel.getApplicationCategoryPreference() == FusedAPIImpl.APP_TYPE_ANY) { - getString(R.string.timeout_desc_gplay) - } else { - getString(R.string.timeout_desc_cleanapk) - }, - positiveButtonText = getString(R.string.retry), - positiveButtonBlock = { - showLoadingUI() - resetTimeoutDialogLock() - mainActivityViewModel.retryFetchingTokenAfterTimeout() - }, - negativeButtonText = - if (updatesViewModel.getApplicationCategoryPreference() == FusedAPIImpl.APP_TYPE_ANY) { - getString(R.string.open_settings) - } else null, - negativeButtonBlock = { - openSettings() - }, - allowCancel = true, - ) - } - }*/ - override fun onTimeout( exception: Exception, predefinedDialog: AlertDialog.Builder @@ -312,19 +285,23 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte WorkManager.getInstance(requireContext()) .getWorkInfosByTagLiveData(UpdatesWorkManager.UPDATES_WORK_NAME) .observe(viewLifecycleOwner) { - val errorStates = - listOf( - WorkInfo.State.FAILED, - WorkInfo.State.BLOCKED, - WorkInfo.State.CANCELLED, - WorkInfo.State.SUCCEEDED - ) - if (!it.isNullOrEmpty() && errorStates.contains(it.last().state)) { - binding.button.isEnabled = true - } + binding.button.isEnabled = hasAnyPendingUpdates(it) } } + private fun hasAnyPendingUpdates( + workInfoList: MutableList + ): Boolean { + val errorStates = listOf( + WorkInfo.State.FAILED, + WorkInfo.State.BLOCKED, + WorkInfo.State.CANCELLED, + WorkInfo.State.SUCCEEDED + ) + return !workInfoList.isNullOrEmpty() && errorStates.contains(workInfoList.last().state) && + updatesViewModel.hasAnyUpdatableApp() && !updatesViewModel.hasAnyPendingAppsForUpdate() + } + override fun showLoadingUI() { binding.button.isEnabled = false binding.noUpdates.visibility = View.GONE @@ -342,7 +319,6 @@ class UpdatesFragment : TimeoutFragment(R.layout.fragment_updates), FusedAPIInte appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { updateProgressOfDownloadingItems(binding.recyclerView, it) } -// resetTimeoutDialogLock() } private fun observeDownloadList() { diff --git a/app/src/main/java/foundation/e/apps/updates/UpdatesViewModel.kt b/app/src/main/java/foundation/e/apps/updates/UpdatesViewModel.kt index 0b546decadb7696bd494dfc514148e13f53d49a0..12b98283daa134aa3614a73f1eb40ca6877c4489 100644 --- a/app/src/main/java/foundation/e/apps/updates/UpdatesViewModel.kt +++ b/app/src/main/java/foundation/e/apps/updates/UpdatesViewModel.kt @@ -90,7 +90,7 @@ class UpdatesViewModel @Inject constructor( } } - suspend fun checkWorkInfoListHasAnyUpdatableWork(workInfoList: List): Boolean { + fun checkWorkInfoListHasAnyUpdatableWork(workInfoList: List): Boolean { workInfoList.forEach { workInfo -> if (listOf( WorkInfo.State.ENQUEUED, @@ -118,4 +118,19 @@ class UpdatesViewModel @Inject constructor( fun getApplicationCategoryPreference(): String { return updatesManagerRepository.getApplicationCategoryPreference() } + + fun hasAnyUpdatableApp(): Boolean { + return updatesList.value?.first?.any { it.status == Status.UPDATABLE || it.status == Status.INSTALLATION_ISSUE } == true + } + + fun hasAnyPendingAppsForUpdate(): Boolean { + val pendingStatesForUpdate = listOf( + Status.QUEUED, + Status.AWAITING, + Status.DOWNLOADING, + Status.DOWNLOADED, + Status.INSTALLING + ) + return updatesList.value?.first?.any { pendingStatesForUpdate.contains(it.status) } == true + } } diff --git a/app/src/main/res/layout/fragment_updates.xml b/app/src/main/res/layout/fragment_updates.xml index 5d701bcbec488b1b505818709d89fa8311405fa1..df89d69f3b40169d683b13776a86d6a743bf14ee 100644 --- a/app/src/main/res/layout/fragment_updates.xml +++ b/app/src/main/res/layout/fragment_updates.xml @@ -60,6 +60,7 @@ android:textAllCaps="false" android:textSize="16sp" app:autoSizeTextType="uniform" + android:enabled="false" app:layout_constraintBottom_toTopOf="@+id/guideline" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/app/src/test/java/foundation/e/apps/FakeFaultyAppDao.kt b/app/src/test/java/foundation/e/apps/FakeFaultyAppDao.kt new file mode 100644 index 0000000000000000000000000000000000000000..776985c831c36b293b623be0c494ee56a2a7c626 --- /dev/null +++ b/app/src/test/java/foundation/e/apps/FakeFaultyAppDao.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 MURENA SAS + * + * 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 . + */ + +package foundation.e.apps + +import foundation.e.apps.api.faultyApps.FaultyApp +import foundation.e.apps.api.faultyApps.FaultyAppDao + +class FakeFaultyAppDao : FaultyAppDao { + + private val faultyAppList = mutableListOf() + + override suspend fun addFaultyApp(faultyApp: FaultyApp): Long { + faultyAppList.add(faultyApp) + return -1 + } + + override suspend fun getFaultyApps(): List { + return faultyAppList + } + + override suspend fun deleteFaultyAppByPackageName(packageName: String): Int { + faultyAppList.removeIf { it.packageName == packageName } + return -1 + } +} diff --git a/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..61585e56c9059338f592a40d5c1872f8df0cc56c --- /dev/null +++ b/app/src/test/java/foundation/e/apps/UpdateManagerImptTest.kt @@ -0,0 +1,397 @@ +/* + * Copyright (C) 2022 MURENA SAS + * + * 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 . + */ + +package foundation.e.apps + +import android.content.pm.ApplicationInfo +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import com.aurora.gplayapi.data.models.AuthData +import foundation.e.apps.api.faultyApps.FaultyAppRepository +import foundation.e.apps.api.fused.FusedAPIRepository +import foundation.e.apps.api.fused.data.FusedApp +import foundation.e.apps.manager.pkg.PkgManagerModule +import foundation.e.apps.updates.manager.UpdatesManagerImpl +import foundation.e.apps.util.MainCoroutineRule +import foundation.e.apps.utils.enums.FilterLevel +import foundation.e.apps.utils.enums.Origin +import foundation.e.apps.utils.enums.ResultStatus +import foundation.e.apps.utils.enums.Status +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.eq + +@OptIn(ExperimentalCoroutinesApi::class) +class UpdateManagerImptTest { + + // Run tasks synchronously + @Rule + @JvmField + val instantExecutorRule = InstantTaskExecutorRule() + + // Sets the main coroutines dispatcher to a TestCoroutineScope for unit testing. + @ExperimentalCoroutinesApi + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + private lateinit var updatesManagerImpl: UpdatesManagerImpl + + @Mock + private lateinit var pkgManagerModule: PkgManagerModule + + @Mock + private lateinit var fusedAPIRepository: FusedAPIRepository + + private lateinit var faultyAppRepository: FaultyAppRepository + + val authData = AuthData("e@e.email", "AtadyMsIAtadyM") + + val applicationInfo = mutableListOf( + ApplicationInfo().apply { this.packageName = "foundation.e.demoone" }, + ApplicationInfo().apply { this.packageName = "foundation.e.demotwo" }, + ApplicationInfo().apply { this.packageName = "foundation.e.demothree" } + ) + + @Before + fun setup() { + MockitoAnnotations.openMocks(this) + faultyAppRepository = FaultyAppRepository(FakeFaultyAppDao()) + updatesManagerImpl = + UpdatesManagerImpl(pkgManagerModule, fusedAPIRepository, faultyAppRepository) + } + + @Test + fun getUpdateWhenUpdateIsAvailable() = runTest { + val gplayApps = mutableListOf( + FusedApp( + _id = "111", + status = Status.UPDATABLE, + name = "Demo One", + package_name = "foundation.e.demoone", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + ) + + val openSourceApps = mutableListOf( + FusedApp( + _id = "113", + status = Status.UPDATABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + origin = Origin.CLEANAPK, + filterLevel = FilterLevel.NONE + ) + ) + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK) + val gplayUpdates = Pair(gplayApps, ResultStatus.OK) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertEquals("fetchUpdate", 2, updateResult.first.size) + } + + @Test + fun getUpdateWhenInstalledPackageListIsEmpty() = runTest { + val authData = AuthData("e@e.email", "AtadyMsIAtadyM") + val applicationInfo = mutableListOf() + Mockito.`when`(pkgManagerModule.getAllUserApps()).thenReturn(applicationInfo) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertEquals("fetchUpdate", 0, updateResult.first.size) + } + + @Test + fun getUpdateWhenUpdateIsUnavailable() = runTest { + val gplayApps = mutableListOf( + FusedApp( + _id = "111", + status = Status.INSTALLED, + name = "Demo One", + package_name = "foundation.e.demoone", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + ) + + val openSourceApps = mutableListOf( + FusedApp( + _id = "113", + status = Status.INSTALLED, + name = "Demo Three", + package_name = "foundation.e.demothree", + origin = Origin.CLEANAPK, + filterLevel = FilterLevel.NONE + ) + ) + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK) + val gplayUpdates = Pair(gplayApps, ResultStatus.OK) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertEquals("fetchUpdate", 0, updateResult.first.size) + } + + @Test + fun getUpdateWhenUpdateHasOnlyForOpenSourceApps() = runTest { + val gplayApps = mutableListOf( + FusedApp( + _id = "111", + status = Status.INSTALLED, + name = "Demo One", + package_name = "foundation.e.demoone", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + FusedApp( + _id = "112", + status = Status.INSTALLED, + name = "Demo Two", + package_name = "foundation.e.demotwo", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + ) + + val openSourceApps = mutableListOf( + FusedApp( + _id = "113", + status = Status.UPDATABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + origin = Origin.CLEANAPK, + filterLevel = FilterLevel.NONE + ) + ) + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK) + val gplayUpdates = Pair(gplayApps, ResultStatus.OK) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertFalse("fetchupdate", updateResult.first.any { it.origin == Origin.GPLAY }) + } + + @Test + fun getUpdateWhenUpdateHasOnlyForGplayApps() = runTest { + val gplayApps = mutableListOf( + FusedApp( + _id = "111", + status = Status.INSTALLED, + name = "Demo One", + package_name = "foundation.e.demoone", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + FusedApp( + _id = "112", + status = Status.UPDATABLE, + name = "Demo Two", + package_name = "foundation.e.demotwo", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + ) + + val openSourceApps = mutableListOf( + FusedApp( + _id = "113", + status = Status.INSTALLED, + name = "Demo Three", + package_name = "foundation.e.demothree", + origin = Origin.CLEANAPK, + filterLevel = FilterLevel.NONE + ) + ) + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK) + val gplayUpdates = Pair(gplayApps, ResultStatus.OK) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertFalse("fetchupdate", updateResult.first.any { it.origin == Origin.CLEANAPK }) + } + + @Test + fun getUpdateWhenFetchingOpenSourceIsFailed() = runTest { + val gplayApps = mutableListOf( + FusedApp( + _id = "111", + status = Status.INSTALLED, + name = "Demo One", + package_name = "foundation.e.demoone", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + FusedApp( + _id = "112", + status = Status.UPDATABLE, + name = "Demo Two", + package_name = "foundation.e.demotwo", + origin = Origin.GPLAY, + filterLevel = FilterLevel.NONE + ), + ) + + val openSourceApps = mutableListOf() + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.TIMEOUT) + val gplayUpdates = Pair(gplayApps, ResultStatus.OK) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertEquals("fetchupdate", 1, updateResult.first.size) + assertEquals("fetchupdate", ResultStatus.TIMEOUT, updateResult.second) + } + + @Test + fun getUpdateWhenFetchingGplayIsFailed() = runTest { + val gplayApps = mutableListOf() + + val openSourceApps = mutableListOf( + FusedApp( + _id = "113", + status = Status.UPDATABLE, + name = "Demo Three", + package_name = "foundation.e.demothree", + origin = Origin.CLEANAPK, + filterLevel = FilterLevel.NONE + ) + ) + + val appList = gplayApps + openSourceApps + val openSourceUpdates = Pair(openSourceApps, ResultStatus.OK) + val gplayUpdates = Pair(gplayApps, ResultStatus.TIMEOUT) + + setupMockingForFetchingUpdates( + applicationInfo, + appList, + authData, + openSourceUpdates, + gplayUpdates + ) + + val updateResult = updatesManagerImpl.getUpdates(authData) + System.out.println("===> updates: ${updateResult.first.map { it.package_name }}") + + assertEquals("fetchupdate", 1, updateResult.first.size) + assertEquals("fetchupdate", ResultStatus.TIMEOUT, updateResult.second) + } + + private suspend fun setupMockingForFetchingUpdates( + applicationInfo: MutableList, + appList: List, + authData: AuthData, + openSourceUpdates: Pair, ResultStatus>, + gplayUpdates: Pair, ResultStatus> + ) { + Mockito.`when`(pkgManagerModule.getAllUserApps()).thenReturn(applicationInfo) + Mockito.`when`( + fusedAPIRepository.getApplicationDetails( + any(), + eq(authData), + eq(Origin.CLEANAPK) + ) + ).thenReturn(openSourceUpdates) + + Mockito.`when`( + fusedAPIRepository.getApplicationDetails( + any(), + eq(authData), + eq(Origin.GPLAY) + ) + ).thenReturn(gplayUpdates) + } +}