diff --git a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt index c67ff219fc..beee23120a 100644 --- a/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt +++ b/android/engine/src/main/java/org/smartregister/fhircore/engine/sync/SyncBroadcaster.kt @@ -16,14 +16,19 @@ package org.smartregister.fhircore.engine.sync +import android.content.Context import com.google.android.fhir.FhirEngine import com.google.android.fhir.sync.AcceptLocalConflictResolver +import com.google.android.fhir.sync.ResourceSyncException +import com.google.android.fhir.sync.Result.Error import com.google.android.fhir.sync.State import com.google.android.fhir.sync.SyncJob import com.google.android.fhir.sync.download.ResourceParamsBasedDownloadWorkManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch +import org.hl7.fhir.r4.model.ResourceType +import org.smartregister.fhircore.engine.R import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry import org.smartregister.fhircore.engine.configuration.app.ConfigService import org.smartregister.fhircore.engine.data.remote.model.response.UserInfo @@ -47,12 +52,16 @@ class SyncBroadcaster( val sharedSyncStatus: MutableSharedFlow = MutableSharedFlow(), val dispatcherProvider: DispatcherProvider = DefaultDispatcherProvider() ) { - fun runSync() { + fun runSync(networkState: (Context) -> Boolean = { NetworkState(it).invoke() }) { CoroutineScope(dispatcherProvider.io()).launch { - NetworkState(sharedPreferencesHelper.context).invoke().apply { + networkState(sharedPreferencesHelper.context).apply { if (this) onRunSync() - else - sharedSyncStatus.emit(State.Failed(com.google.android.fhir.sync.Result.Error(listOf()))) + else { + val message = sharedPreferencesHelper.context.getString(R.string.unable_to_sync) + val resourceSyncException = + listOf(ResourceSyncException(ResourceType.Flag, java.lang.Exception(message))) + sharedSyncStatus.emit(State.Failed(Error(resourceSyncException))) + } } } } diff --git a/android/engine/src/main/res/values/strings.xml b/android/engine/src/main/res/values/strings.xml index 9bdd84489b..fe6525ea48 100644 --- a/android/engine/src/main/res/values/strings.xml +++ b/android/engine/src/main/res/values/strings.xml @@ -134,4 +134,5 @@ Session expired Unassigned Please click BACK again to exit + Unable to sync. Connect to the internet and try again diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/NetworkStateTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/NetworkStateTest.kt index d92cac5f70..5aed8a5a8b 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/NetworkStateTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/NetworkStateTest.kt @@ -19,8 +19,12 @@ package org.smartregister.fhircore.engine.sync import android.app.Application import android.net.ConnectivityManager import android.net.NetworkCapabilities +import android.os.Build import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.core.app.ApplicationProvider +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import org.junit.Rule import org.junit.Test import org.junit.jupiter.api.Assertions.assertTrue @@ -37,17 +41,26 @@ internal class NetworkStateTest : RobolectricTest() { @Suppress("DEPRECATION") @Test fun invoke() { - val connectivityManager = context.getSystemService(ConnectivityManager::class.java) + val networkState = mockk() + every { networkState.invoke() } returns false + + networkState.invoke() + + verify { networkState.invoke() } + val connectivityManager = context.getSystemService(ConnectivityManager::class.java) val networkCapabilities = ShadowNetworkCapabilities.newInstance() - shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - shadowOf(connectivityManager) - .setNetworkCapabilities(connectivityManager.activeNetwork, networkCapabilities) - - assertTrue(connectivityManager.activeNetworkInfo?.type != ConnectivityManager.TYPE_WIFI) - assertTrue(connectivityManager.activeNetworkInfo?.type == ConnectivityManager.TYPE_MOBILE) - assertTrue(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) - assertTrue(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + shadowOf(connectivityManager) + .setNetworkCapabilities(connectivityManager.activeNetwork, networkCapabilities) + shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + shadowOf(networkCapabilities).addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + assertTrue(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) + assertTrue(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + } else { + assertTrue(connectivityManager.activeNetworkInfo?.type != ConnectivityManager.TYPE_WIFI) + assertTrue(connectivityManager.activeNetworkInfo?.type == ConnectivityManager.TYPE_MOBILE) + } } } diff --git a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt index 5f7e9ca47d..b9f0e5468f 100644 --- a/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt +++ b/android/engine/src/test/java/org/smartregister/fhircore/engine/sync/SyncBroadcasterTest.kt @@ -16,50 +16,26 @@ package org.smartregister.fhircore.engine.sync -import android.content.Context -import com.google.android.fhir.FhirEngine -import com.google.android.fhir.sync.State -import com.google.android.fhir.sync.SyncJob +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.MutableSharedFlow -import org.junit.Before -import org.mockito.Mock -import org.smartregister.fhircore.engine.configuration.ConfigurationRegistry -import org.smartregister.fhircore.engine.configuration.app.ConfigService -import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceDataSource -import org.smartregister.fhircore.engine.data.remote.fhir.resource.FhirResourceService -import org.smartregister.fhircore.engine.util.DispatcherProvider -import org.smartregister.fhircore.engine.util.SharedPreferencesHelper +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Test +import org.smartregister.fhircore.engine.robolectric.RobolectricTest @ExperimentalCoroutinesApi -internal class SyncBroadcasterTest { +class SyncBroadcasterTest : RobolectricTest() { - @Mock private lateinit var fhirResourceService: FhirResourceService - private lateinit var fhirResourceDataSource: FhirResourceDataSource - @Mock private lateinit var configService: ConfigService - @Mock private lateinit var context: Context - @Mock private lateinit var syncJob: SyncJob - @Mock private lateinit var fhirEngine: FhirEngine - @Mock private lateinit var dispatcherProvider: DispatcherProvider - @Mock private lateinit var configurationRegistry: ConfigurationRegistry - private val sharedSyncStatus: MutableSharedFlow = MutableSharedFlow() - - private lateinit var sharedPreferencesHelper: SharedPreferencesHelper private lateinit var syncBroadcaster: SyncBroadcaster - @Before - fun setup() { - fhirResourceDataSource = FhirResourceDataSource(fhirResourceService) - sharedPreferencesHelper = SharedPreferencesHelper(context) - syncBroadcaster = - SyncBroadcaster( - configurationRegistry, - sharedPreferencesHelper, - configService, - syncJob, - fhirEngine, - sharedSyncStatus, - dispatcherProvider - ) + @Test + fun runSync() { + runBlockingTest { + syncBroadcaster = mockk() + every { syncBroadcaster.runSync() } returns Unit + syncBroadcaster.runSync() + verify { syncBroadcaster.runSync() } + } } } diff --git a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt index ba9c8a596c..3c2fec58ea 100644 --- a/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt +++ b/android/quest/src/main/java/org/smartregister/fhircore/quest/ui/main/AppMainActivity.kt @@ -103,6 +103,12 @@ open class AppMainActivity : BaseMultiLanguageActivity(), OnSyncListener { Timber.w(state.exceptions.joinToString { it.exception.message.toString() }) } is State.Failed -> { + if (state.result.exceptions.isNotEmpty() && + state.result.exceptions.first().resourceType == ResourceType.Flag + ) { + showToast(state.result.exceptions.first().exception.message!!) + return + } showToast(getString(R.string.sync_failed_text)) val hasAuthError = state.result.exceptions.any {