From 86dd0d200d5802974ea6494bdeafabf43096338b Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Wed, 26 Aug 2026 13:13:31 +0530 Subject: [PATCH] refactor: migrate data sharing consent logic to SurveyRepository and relocate UseCase test to common module --- .../android/di/UseCaseModule.kt | 5 + .../android/repository/SurveyRepository.kt | 7 ++ .../HomeScreenMapContainerViewModel.kt | 7 +- .../GetDataSharingTermsUseCaseTest.kt | 97 ------------------- .../repository/SurveyRepositoryInterface.kt | 6 ++ .../survey}/GetDataSharingTermsUseCase.kt | 23 ++--- .../survey/GetDataSharingTermsUseCaseTest.kt | 93 ++++++++++++++++++ .../testing/FakeSurveyRepository.kt | 9 ++ 8 files changed, 135 insertions(+), 112 deletions(-) delete mode 100644 app/src/test/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCaseTest.kt rename {app/src/main/java/org/groundplatform/android/usecases/datasharingterms => core/domain/src/commonMain/kotlin/org/groundplatform/domain/usecases/survey}/GetDataSharingTermsUseCase.kt (65%) create mode 100644 core/domain/src/commonTest/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCaseTest.kt diff --git a/app/src/main/java/org/groundplatform/android/di/UseCaseModule.kt b/app/src/main/java/org/groundplatform/android/di/UseCaseModule.kt index b40f142886..14a939b496 100644 --- a/app/src/main/java/org/groundplatform/android/di/UseCaseModule.kt +++ b/app/src/main/java/org/groundplatform/android/di/UseCaseModule.kt @@ -27,6 +27,7 @@ import org.groundplatform.domain.repository.SurveyRepositoryInterface import org.groundplatform.domain.repository.UserRepositoryInterface import org.groundplatform.domain.usecases.GetLoiReportUseCase import org.groundplatform.domain.usecases.submission.SubmitDataUseCase +import org.groundplatform.domain.usecases.survey.GetDataSharingTermsUseCase import org.groundplatform.domain.usecases.survey.GetSurveyListItemUseCase import org.groundplatform.domain.usecases.survey.RemoveOfflineSurveyUseCase import org.groundplatform.domain.usecases.survey.SyncSurveyUseCase @@ -90,4 +91,8 @@ object UseCaseModule { surveyRepository: SurveyRepositoryInterface, mapStateRepository: MapStateRepositoryInterface, ) = RemoveOfflineSurveyUseCase(surveyRepository, mapStateRepository) + + @Provides + fun providesGetDataSharingTermsUseCase(surveyRepository: SurveyRepositoryInterface) = + GetDataSharingTermsUseCase(surveyRepository) } diff --git a/app/src/main/java/org/groundplatform/android/repository/SurveyRepository.kt b/app/src/main/java/org/groundplatform/android/repository/SurveyRepository.kt index e85d44264b..8eeb644849 100644 --- a/app/src/main/java/org/groundplatform/android/repository/SurveyRepository.kt +++ b/app/src/main/java/org/groundplatform/android/repository/SurveyRepository.kt @@ -132,4 +132,11 @@ constructor( override suspend fun unsubscribeFromSurveyUpdates(surveyId: String) { remoteDataStore.unsubscribeFromSurveyUpdates(surveyId) } + + override fun getDataSharingConsent(surveyId: String): Boolean = + localValueStore.getDataSharingConsent(surveyId) + + override fun setDataSharingConsent(surveyId: String, consent: Boolean) { + localValueStore.setDataSharingConsent(surveyId, consent) + } } diff --git a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt index 0f2dc98557..b1e84647d9 100644 --- a/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/home/mapcontainer/HomeScreenMapContainerViewModel.kt @@ -41,7 +41,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.groundplatform.android.R import org.groundplatform.android.common.Constants.CLUSTERING_ZOOM_THRESHOLD -import org.groundplatform.android.data.local.LocalValueStore import org.groundplatform.android.system.LocationManager import org.groundplatform.android.system.PermissionsManager import org.groundplatform.android.system.SettingsManager @@ -55,7 +54,6 @@ import org.groundplatform.android.ui.home.mapcontainer.jobs.SelectedLoiSheetData import org.groundplatform.android.ui.map.Feature import org.groundplatform.android.ui.map.gms.GmsExt.area import org.groundplatform.android.ui.util.getDefaultColor -import org.groundplatform.android.usecases.datasharingterms.GetDataSharingTermsUseCase import org.groundplatform.domain.model.Survey import org.groundplatform.domain.model.job.Job import org.groundplatform.domain.model.locationofinterest.LocationOfInterest @@ -66,6 +64,7 @@ import org.groundplatform.domain.repository.SubmissionRepositoryInterface import org.groundplatform.domain.repository.SurveyRepositoryInterface import org.groundplatform.domain.repository.UserRepositoryInterface import org.groundplatform.domain.usecases.GetLoiReportUseCase +import org.groundplatform.domain.usecases.survey.GetDataSharingTermsUseCase import org.groundplatform.feature.pdf.LoiReportExporter import org.groundplatform.ui.components.loireport.LoiReportAction import timber.log.Timber @@ -85,7 +84,6 @@ internal constructor( permissionsManager: PermissionsManager, private val surveyRepository: SurveyRepositoryInterface, private val userRepository: UserRepositoryInterface, - private val localValueStore: LocalValueStore, private val locationOfInterestHelper: LocationOfInterestHelper, private val getLoiReportUseCase: GetLoiReportUseCase, private val loiReportExporter: LoiReportExporter, @@ -300,9 +298,10 @@ internal constructor( featureClicked.value = features.minByOrNull { it.geometry.area() } } + /** Records user consent to the data sharing terms of the active survey. */ fun grantDataSharingConsent() { val survey = requireNotNull(surveyRepository.activeSurvey) - localValueStore.setDataSharingConsent(survey.id, true) + surveyRepository.setDataSharingConsent(survey.id, true) } /** diff --git a/app/src/test/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCaseTest.kt b/app/src/test/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCaseTest.kt deleted file mode 100644 index 1783895dba..0000000000 --- a/app/src/test/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCaseTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.groundplatform.android.usecases.datasharingterms - -import com.google.common.truth.Truth.assertThat -import dagger.hilt.android.testing.HiltAndroidTest -import javax.inject.Inject -import org.groundplatform.android.BaseHiltTest -import org.groundplatform.android.FakeData.SURVEY -import org.groundplatform.android.data.local.LocalValueStore -import org.groundplatform.android.data.remote.FakeRemoteDataStore -import org.groundplatform.android.proto.Survey.DataSharingTerms -import org.groundplatform.android.proto.SurveyKt.dataSharingTerms -import org.groundplatform.android.usecases.survey.ActivateSurveyUseCase -import org.groundplatform.domain.model.Survey -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@HiltAndroidTest -@RunWith(RobolectricTestRunner::class) -class GetDataSharingTermsUseCaseTest : BaseHiltTest() { - @Inject lateinit var activateSurveyUseCase: ActivateSurveyUseCase - @Inject lateinit var fakeRemoteDataStore: FakeRemoteDataStore - @Inject lateinit var getDataSharingTermsUseCase: GetDataSharingTermsUseCase - @Inject lateinit var localValueStore: LocalValueStore - - private fun activateSurvey(survey: Survey) = runWithTestDispatcher { - fakeRemoteDataStore.surveys = listOf(survey) - activateSurveyUseCase(survey.id) - } - - @Test - fun `Fails with exception if no survey active`() { - val result = getDataSharingTermsUseCase() - - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()).isInstanceOf(IllegalStateException::class.java) - assertThat(result.exceptionOrNull()?.message).isEqualTo("No active survey") - } - - @Test - fun `Fails with custom exception if custom data sharing terms are invalid`() { - val survey = SURVEY.copy(dataSharingTerms = Survey.DataSharingTerms.Custom("")) - activateSurvey(survey) - - val result = getDataSharingTermsUseCase() - - assertThat(result.isFailure).isTrue() - assertThat(result.exceptionOrNull()) - .isInstanceOf(GetDataSharingTermsUseCase.InvalidCustomSharingTermsException::class.java) - } - - @Test - fun `Succeeds with null if data sharing terms is already accepted`() { - activateSurvey(SURVEY) - localValueStore.setDataSharingConsent(SURVEY.id, true) - - val result = getDataSharingTermsUseCase() - - assertThat(result.isSuccess).isTrue() - assertThat(result.getOrNull()).isNull() - } - - @Test - fun `Succeeds with null if data sharing terms is missing`() { - activateSurvey(SURVEY.copy(dataSharingTerms = null)) - - val result = getDataSharingTermsUseCase() - - assertThat(result.isSuccess).isTrue() - assertThat(result.getOrNull()).isNull() - } - - @Test - fun `Succeeds with data sharing terms if not already accepted`() { - activateSurvey(SURVEY) - - val result = getDataSharingTermsUseCase() - - assertThat(result.isSuccess).isTrue() - assertThat(result.getOrNull()).isEqualTo(SURVEY.dataSharingTerms) - } -} diff --git a/core/domain/src/commonMain/kotlin/org/groundplatform/domain/repository/SurveyRepositoryInterface.kt b/core/domain/src/commonMain/kotlin/org/groundplatform/domain/repository/SurveyRepositoryInterface.kt index fc7d8cdeb0..687bcacb44 100644 --- a/core/domain/src/commonMain/kotlin/org/groundplatform/domain/repository/SurveyRepositoryInterface.kt +++ b/core/domain/src/commonMain/kotlin/org/groundplatform/domain/repository/SurveyRepositoryInterface.kt @@ -48,4 +48,10 @@ interface SurveyRepositoryInterface { suspend fun subscribeToSurveyUpdates(surveyId: String) suspend fun unsubscribeFromSurveyUpdates(surveyId: String) + + /** Returns true if the user has agreed to data sharing terms for the given [surveyId]. */ + fun getDataSharingConsent(surveyId: String): Boolean + + /** Records the user's [consent] to data sharing terms for the given [surveyId]. */ + fun setDataSharingConsent(surveyId: String, consent: Boolean) } diff --git a/app/src/main/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCase.kt b/core/domain/src/commonMain/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCase.kt similarity index 65% rename from app/src/main/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCase.kt rename to core/domain/src/commonMain/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCase.kt index 8022a27ce3..8f64f3699a 100644 --- a/app/src/main/java/org/groundplatform/android/usecases/datasharingterms/GetDataSharingTermsUseCase.kt +++ b/core/domain/src/commonMain/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCase.kt @@ -13,25 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.groundplatform.android.usecases.datasharingterms -import javax.inject.Inject -import org.groundplatform.android.data.local.LocalValueStore +package org.groundplatform.domain.usecases.survey + import org.groundplatform.domain.model.Survey import org.groundplatform.domain.repository.SurveyRepositoryInterface -class GetDataSharingTermsUseCase -@Inject -constructor( - private val localValueStore: LocalValueStore, - private val surveyRepository: SurveyRepositoryInterface, -) { +/** + * Returns the data sharing terms for the currently active survey, if not already accepted. + * + * Returns [Result.success] with `null` if the survey has no terms or the user has already accepted + * them. Returns [Result.failure] with [InvalidCustomSharingTermsException] if custom terms text is + * blank, or [IllegalStateException] if no survey is currently active. + */ +class GetDataSharingTermsUseCase(private val surveyRepository: SurveyRepositoryInterface) { - /** Returns the data sharing terms for the currently active survey, if not already accepted. */ operator fun invoke(): Result = runCatching { val survey = surveyRepository.activeSurvey ?: error("No active survey") val sharingTerms = survey.dataSharingTerms - if (sharingTerms == null || localValueStore.getDataSharingConsent(survey.id)) { + if (sharingTerms == null || surveyRepository.getDataSharingConsent(survey.id)) { // User previously agreed to the terms or data sharing terms are missing. return Result.success(null) } @@ -41,5 +41,6 @@ constructor( return Result.success(sharingTerms) } + /** Thrown when a survey defines custom data sharing terms with blank text. */ class InvalidCustomSharingTermsException : Exception() } diff --git a/core/domain/src/commonTest/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCaseTest.kt b/core/domain/src/commonTest/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCaseTest.kt new file mode 100644 index 0000000000..7b185b682a --- /dev/null +++ b/core/domain/src/commonTest/kotlin/org/groundplatform/domain/usecases/survey/GetDataSharingTermsUseCaseTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.groundplatform.domain.usecases.survey + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlinx.coroutines.test.runTest +import org.groundplatform.domain.model.Survey +import org.groundplatform.testing.FakeDataGenerator +import org.groundplatform.testing.FakeSurveyRepository + +class GetDataSharingTermsUseCaseTest { + private val surveyRepository = FakeSurveyRepository() + private val getDataSharingTermsUseCase = GetDataSharingTermsUseCase(surveyRepository) + + private suspend fun activateSurvey(survey: Survey) { + surveyRepository.offlineSurveys = listOf(survey) + surveyRepository.activateSurvey(survey.id) + } + + @Test + fun `Fails with exception if no survey active`() { + val result = getDataSharingTermsUseCase() + + assertTrue(result.isFailure) + assertIs(result.exceptionOrNull()) + assertEquals("No active survey", result.exceptionOrNull()?.message) + } + + @Test + fun `Fails with custom exception if custom data sharing terms are invalid`() = runTest { + val survey = FakeDataGenerator.newSurvey(dataSharingTerms = Survey.DataSharingTerms.Custom("")) + activateSurvey(survey) + + val result = getDataSharingTermsUseCase() + + assertTrue(result.isFailure) + assertIs( + result.exceptionOrNull() + ) + } + + @Test + fun `Succeeds with null if data sharing terms is already accepted`() = runTest { + val survey = FakeDataGenerator.newSurvey() + activateSurvey(survey) + surveyRepository.setDataSharingConsent(survey.id, true) + + val result = getDataSharingTermsUseCase() + + assertTrue(result.isSuccess) + assertNull(result.getOrNull()) + } + + @Test + fun `Succeeds with null if data sharing terms is missing`() = runTest { + val survey = FakeDataGenerator.newSurvey().copy(dataSharingTerms = null) + activateSurvey(survey) + + val result = getDataSharingTermsUseCase() + + assertTrue(result.isSuccess) + assertNull(result.getOrNull()) + } + + @Test + fun `Succeeds with data sharing terms if not already accepted`() = runTest { + val survey = FakeDataGenerator.newSurvey() + activateSurvey(survey) + + val result = getDataSharingTermsUseCase() + + assertTrue(result.isSuccess) + assertEquals(survey.dataSharingTerms, result.getOrNull()) + } +} diff --git a/core/testing/src/commonMain/kotlin/org/groundplatform/testing/FakeSurveyRepository.kt b/core/testing/src/commonMain/kotlin/org/groundplatform/testing/FakeSurveyRepository.kt index a04575b008..dc9812de39 100644 --- a/core/testing/src/commonMain/kotlin/org/groundplatform/testing/FakeSurveyRepository.kt +++ b/core/testing/src/commonMain/kotlin/org/groundplatform/testing/FakeSurveyRepository.kt @@ -40,6 +40,8 @@ class FakeSurveyRepository : SurveyRepositoryInterface { val onGetRemoteSurveyCall = FakeCall { id -> remoteSurveys.find { it.id == id } } + private val dataSharingConsentMap = mutableMapOf() + override suspend fun saveSurvey(survey: Survey) { offlineSurveys = offlineSurveys + survey } @@ -75,4 +77,11 @@ class FakeSurveyRepository : SurveyRepositoryInterface { override suspend fun unsubscribeFromSurveyUpdates(surveyId: String) { subscribedSurveyIds.remove(surveyId) } + + override fun getDataSharingConsent(surveyId: String): Boolean = + dataSharingConsentMap[surveyId] ?: false + + override fun setDataSharingConsent(surveyId: String, consent: Boolean) { + dataSharingConsentMap[surveyId] = consent + } }