From 79a6b96590b72cf799dfe0dd6d7f631fb7ce8a58 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 30 Apr 2024 11:55:06 +0200 Subject: [PATCH 1/2] Perform sync in local network Signed-off-by: Pablo --- .../usescases/flow/syncFlow/SyncFlowTest.kt | 13 ++++- .../sync/MockedWorkManagerController.kt | 8 --- .../utils/granularsync/GranularSyncModule.kt | 18 +++--- .../assets/mocks/systeminfo/ping.txt | 1 + .../utils/granularsync/GranularSyncModule.kt | 2 + .../workManager/WorkManagerController.kt | 1 - .../workManager/WorkManagerControllerImpl.kt | 39 ------------- .../granularsync/GranularSyncPresenter.kt | 14 +++++ .../granularsync/GranularSyncRepository.kt | 9 ++- .../utils/granularsync/SyncStatusDialog.kt | 18 +++--- .../granularsync/GranularSyncPresenterTest.kt | 58 +++++++++++++++++++ gradle/libs.versions.toml | 2 +- 12 files changed, 117 insertions(+), 66 deletions(-) create mode 100644 app/src/dhisUITesting/assets/mocks/systeminfo/ping.txt diff --git a/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt b/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt index 458845c14f..7e24f017dc 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/flow/syncFlow/SyncFlowTest.kt @@ -20,13 +20,12 @@ import org.dhis2.usescases.searchTrackEntity.SearchTEActivity import org.dhis2.usescases.searchte.robot.searchTeiRobot import org.dhis2.usescases.teidashboard.robot.eventRobot import org.dhis2.usescases.teidashboard.robot.teiDashboardRobot -import org.junit.Ignore +import org.hisp.dhis.android.core.mockwebserver.ResponseController.GET import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import syncFlowRobot import java.util.UUID -import org.dhis2.usescases.eventsWithoutRegistration.eventCapture.EventCaptureActivity @RunWith(AndroidJUnit4::class) class SyncFlowTest : BaseTest() { @@ -48,12 +47,15 @@ class SyncFlowTest : BaseTest() { override fun setUp() { super.setUp() + setupMockServer() workInfoStatusLiveData = ApplicationProvider.getApplicationContext().mutableWorkInfoStatuses } @Test fun shouldShowErrorWhenTEISyncFails() { + mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) + val teiName = "Lars" val teiLastName = "Overland" @@ -95,6 +97,8 @@ class SyncFlowTest : BaseTest() { @Test fun shouldSuccessfullySyncSavedEvent() { + mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) + prepareMalariaEventIntentAndLaunchActivity(ruleEventWithoutRegistration) eventWithoutRegistrationRobot(composeTestRule) { @@ -118,6 +122,8 @@ class SyncFlowTest : BaseTest() { @Test fun shouldShowErrorWhenSyncEventFails() { + mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) + prepareMalariaEventIntentAndLaunchActivity(ruleEventWithoutRegistration) eventWithoutRegistrationRobot(composeTestRule) { @@ -141,6 +147,8 @@ class SyncFlowTest : BaseTest() { @Test fun shouldSuccessfullySyncSavedDataSet() { + mockWebServerRobot.addResponse(GET, "/api/system/ping", API_PING_RESPONSE_OK) + prepareFacilityDataSetIntentAndLaunchActivity(ruleDataSet) dataSetRobot { @@ -223,5 +231,6 @@ class SyncFlowTest : BaseTest() { companion object { const val LAB_MONITORING_EVENT_DATE = "28/6/2020" + const val API_PING_RESPONSE_OK = "mocks/systeminfo/ping.txt" } } \ No newline at end of file diff --git a/app/src/androidTest/java/org/dhis2/usescases/sync/MockedWorkManagerController.kt b/app/src/androidTest/java/org/dhis2/usescases/sync/MockedWorkManagerController.kt index aabd81897a..2e5bee9767 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/sync/MockedWorkManagerController.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/sync/MockedWorkManagerController.kt @@ -15,14 +15,6 @@ class MockedWorkManagerController(private val workInfoStatuses: LiveData() + val serverAvailability: LiveData = _serverAvailability + private fun loadSyncInfo(forcedState: State? = null) { viewModelScope.launch(dispatcher.io()) { val syncState = async { @@ -424,4 +427,15 @@ class GranularSyncPresenter( } } } + + fun checkServerAvailability() { + viewModelScope.launch { + try { + repository.checkServerAvailability() + _serverAvailability.value = true + } catch (error: RuntimeException) { + _serverAvailability.value = false + } + } + } } diff --git a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt index 59a0f0ef83..61ae035dd1 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/GranularSyncRepository.kt @@ -1,6 +1,7 @@ package org.dhis2.utils.granularsync import io.reactivex.Single +import kotlinx.coroutines.withContext import org.dhis2.R import org.dhis2.commons.bindings.categoryOptionCombo import org.dhis2.commons.bindings.countEventImportConflicts @@ -41,6 +42,7 @@ import org.dhis2.commons.sync.ConflictType import org.dhis2.commons.sync.SyncContext import org.dhis2.commons.sync.SyncStatusItem import org.dhis2.commons.sync.SyncStatusType +import org.dhis2.commons.viewmodel.DispatcherProvider import org.dhis2.data.dhislogic.DhisProgramUtils import org.hisp.dhis.android.core.D2 import org.hisp.dhis.android.core.common.State @@ -57,6 +59,7 @@ class GranularSyncRepository( private val dhisProgramUtils: DhisProgramUtils, private val periodUtils: DhisPeriodUtils, private val resourceManager: ResourceManager, + private val dispatcher: DispatcherProvider, ) { fun getUiState(forcedState: State? = null): SyncUiState { @@ -554,7 +557,7 @@ class GranularSyncRepository( val teiMainAttribute = tei.let { d2.teiMainAttributes(it.uid(), programUid) - } ?: emptyList() + } val label = teiMainAttribute.firstOrNull()?.let { (attributeName, value) -> "$attributeName: $value" @@ -980,6 +983,10 @@ class GranularSyncRepository( .map { it.displayName() } } } + + suspend fun checkServerAvailability() = withContext(dispatcher.io()) { + d2.systemInfoModule().ping().blockingGet() + } } fun List.sortedByState(): List { diff --git a/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt b/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt index 088f59c5b0..d054fec435 100644 --- a/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt +++ b/app/src/main/java/org/dhis2/utils/granularsync/SyncStatusDialog.kt @@ -181,14 +181,18 @@ class SyncStatusDialog : BottomSheetDialogFragment(), GranularSyncContracts.View } private fun onSyncClick() { - when { - networkUtils.isOnline() -> syncGranular() - viewModel.canSendSMS() && - viewModel.isSMSEnabled(context?.showSMS() == true) -> syncSms() - - !networkUtils.isOnline() && - !viewModel.isSMSEnabled(context?.showSMS() == true) -> showSnackbar() + viewModel.serverAvailability.observe(viewLifecycleOwner) { isAvailable -> + val canSendSMS = viewModel.canSendSMS() + val isSMSEnabled = viewModel.isSMSEnabled(context?.showSMS() == true) + + when { + isAvailable -> syncGranular() + canSendSMS && isSMSEnabled -> syncSms() + else -> showSnackbar() + } } + + viewModel.checkServerAvailability() } private fun showSnackbar() { diff --git a/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt b/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt index a000609fd8..9214df8b14 100644 --- a/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt +++ b/app/src/test/java/org/dhis2/utils/granularsync/GranularSyncPresenterTest.kt @@ -8,8 +8,12 @@ import androidx.lifecycle.Observer import androidx.work.WorkInfo import io.reactivex.Completable import io.reactivex.Single +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain import org.dhis2.commons.Constants import org.dhis2.commons.sync.ConflictType import org.dhis2.commons.sync.SyncContext @@ -23,14 +27,18 @@ import org.hisp.dhis.android.core.common.ObjectWithUid import org.hisp.dhis.android.core.common.State import org.hisp.dhis.android.core.dataset.DataSet import org.hisp.dhis.android.core.dataset.DataSetElement +import org.junit.After import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Before import org.junit.Rule import org.junit.Test import org.mockito.ArgumentMatchers.anyList import org.mockito.Mockito import org.mockito.kotlin.any import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -57,6 +65,16 @@ class GranularSyncPresenterTest { private val context: Context = mock() private val syncContext: SyncContext = SyncContext.Global() + @Before + fun setUp() { + Dispatchers.setMain(testingDispatcher) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + @Test fun `should return tracker program error state`() { val presenter = GranularSyncPresenter( @@ -463,4 +481,44 @@ class GranularSyncPresenterTest { verify(workManager).syncDataForWorker(Constants.DATA_NOW, Constants.INITIAL_SYNC) verify(workInfoObserver).onChanged(anyList()) } + + @Test + fun shouldCheckAvailableConnection() = runBlocking { + whenever(repository.checkServerAvailability()) doReturn "pong" + + val presenter = GranularSyncPresenter( + d2, + view, + repository, + trampolineSchedulerProvider, + testDispatcher, + SyncContext.Global(), + workManager, + smsSyncProvider, + ) + + presenter.checkServerAvailability() + + assertTrue(presenter.serverAvailability.value!!) + } + + @Test + fun shouldCheckUnavailableConnection() = runBlocking { + whenever(repository.checkServerAvailability()) doThrow RuntimeException() + + val presenter = GranularSyncPresenter( + d2, + view, + repository, + trampolineSchedulerProvider, + testDispatcher, + SyncContext.Global(), + workManager, + smsSyncProvider, + ) + + presenter.checkServerAvailability() + + assertFalse(presenter.serverAvailability.value!!) + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 21bedb2746..fbc18b6836 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ hilt = '2.47' hiltCompiler = '1.0.0' jacoco = '0.8.10' designSystem = "0.2-20240426.091900-58" -dhis2sdk = "1.10.0-20240322.092247-38" +dhis2sdk = "1.10.0-20240426.151240-44" ruleEngine = "3.0.0-20240119.134348-12" expressionParser = "1.1.0-20240219.115041-14" appcompat = "1.6.1" From 982bd28a93ab48d4ef5af7e210407d26acbff798 Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 30 Apr 2024 13:20:03 +0200 Subject: [PATCH 2/2] Perform sync in local network Signed-off-by: Pablo --- .../org/dhis2/usescases/teidashboard/robot/EnrollmentRobot.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/EnrollmentRobot.kt b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/EnrollmentRobot.kt index e55593ce66..12eef6b090 100644 --- a/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/EnrollmentRobot.kt +++ b/app/src/androidTest/java/org/dhis2/usescases/teidashboard/robot/EnrollmentRobot.kt @@ -33,7 +33,7 @@ fun enrollmentRobot(enrollmentRobot: EnrollmentRobot.() -> Unit) { class EnrollmentRobot : BaseRobot() { fun clickOnAProgramForEnrollment(composeTestRule: ComposeTestRule, program: String) { - composeTestRule.onNodeWithTag(PROGRAM_TO_ENROLL.format(program)) + composeTestRule.onNodeWithTag(PROGRAM_TO_ENROLL.format(program), useUnmergedTree = true) .performClick() }