From 683c290dedf9dbf0c8a19f37f5f10fef5d4c055f Mon Sep 17 00:00:00 2001 From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com> Date: Fri, 20 Nov 2020 12:37:36 +0100 Subject: [PATCH] Persist consent to key submission (EXPOSUREAPP-3734) (#1633) * create SubmissionSettings and add to SubmissionRepository * refactor DI * refactor testcode * refactoring and test code * klint * remove AssistedInject * split consent method Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com> --- .../storage/KeyCacheDatabaseTest.kt | 6 +- .../ui/submission/SubmissionSettingsTest.kt | 24 +++ ...isTestResultRetrievalPeriodicWorkerTest.kt | 9 +- .../de/rki/coronawarnapp/playbook/Playbook.kt | 8 +- .../service/submission/SubmissionService.kt | 70 ++----- .../storage/RiskLevelRepository.kt | 2 - .../storage/SubmissionRepository.kt | 175 ++++++++++++------ .../submission/SubmissionSettings.kt | 22 +++ .../submission/SubmissionTask.kt | 4 +- .../ui/main/home/HomeFragmentViewModel.kt | 12 +- .../main/home/SubmissionCardsStateProvider.kt | 8 +- .../ui/settings/SettingsResetViewModel.kt | 4 +- .../fragment/SubmissionDispatcherFragment.kt | 6 +- .../scan/SubmissionQRCodeScanViewModel.kt | 12 +- .../submission/tan/SubmissionTanViewModel.kt | 8 +- .../SubmissionTestResultFragment.kt | 5 +- .../SubmissionTestResultViewModel.kt | 22 ++- ...sionResultPositiveOtherWarningViewModel.kt | 4 +- .../ui/viewmodel/SubmissionViewModel.kt | 11 +- .../de/rki/coronawarnapp/util/DataReset.kt | 5 +- ...gnosisTestResultRetrievalPeriodicWorker.kt | 7 +- .../main/home/HomeFragmentViewModelTest.kt | 5 +- .../home/SubmissionCardsStateProviderTest.kt | 12 +- .../submission/SubmissionServiceTest.kt | 94 ++-------- .../storage/SubmissionRepositoryTest.kt | 115 ++++++++++++ .../scan/SubmissionQRCodeScanViewModelTest.kt | 4 +- .../tan/SubmissionTanViewModelTest.kt | 13 +- 27 files changed, 409 insertions(+), 258 deletions(-) create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSettingsTest.kt create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabaseTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabaseTest.kt index 62230915d..fc2bfbe62 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabaseTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/diagnosiskeys/storage/KeyCacheDatabaseTest.kt @@ -40,7 +40,7 @@ class KeyCacheDatabaseTest { dao.insertEntry(keyDay) dao.insertEntry(keyHour) - dao.getAllEntries() shouldBe listOf(keyDay, keyHour) + dao.allEntries() shouldBe listOf(keyDay, keyHour) dao.getEntriesForType(CachedKeyInfo.Type.LOCATION_DAY.typeValue) shouldBe listOf(keyDay) dao.getEntriesForType(CachedKeyInfo.Type.LOCATION_HOUR.typeValue) shouldBe listOf(keyHour) @@ -65,7 +65,7 @@ class KeyCacheDatabaseTest { } dao.deleteEntry(keyDay) - dao.getAllEntries() shouldBe listOf( + dao.allEntries() shouldBe listOf( keyHour.copy( isDownloadComplete = true, etag = "with milk" @@ -73,7 +73,7 @@ class KeyCacheDatabaseTest { ) dao.clear() - dao.getAllEntries() shouldBe emptyList() + dao.allEntries() shouldBe emptyList<List<CachedKeyInfo>>() } } } diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSettingsTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSettingsTest.kt new file mode 100644 index 000000000..fa26c51fb --- /dev/null +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSettingsTest.kt @@ -0,0 +1,24 @@ +package de.rki.coronawarnapp.ui.submission + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import de.rki.coronawarnapp.submission.SubmissionSettings +import io.kotest.matchers.shouldBe +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class SubmissionSettingsTest { + + private val appContext: Context + get() = ApplicationProvider.getApplicationContext() + + @Test + fun consentIsPersisted() { + val settings = SubmissionSettings(appContext) + settings.hasGivenConsent.value shouldBe false + settings.hasGivenConsent.update { true } + settings.hasGivenConsent.value shouldBe true + } +} diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt index c610d6345..5544f81e8 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorkerTest.kt @@ -8,12 +8,14 @@ import androidx.work.WorkManager import androidx.work.WorkRequest import androidx.work.testing.TestDriver import androidx.work.testing.WorkManagerTestInitHelper +import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.TimeAndDateExtensions.daysToMilliseconds import de.rki.coronawarnapp.util.formatter.TestResult import io.mockk.coEvery import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.mockk import io.mockk.mockkObject import io.mockk.slot @@ -35,7 +37,8 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest { private lateinit var context: Context private lateinit var workManager: WorkManager private lateinit var request: WorkRequest - private lateinit var request2: WorkRequest + @MockK lateinit var playbook: Playbook + private val submissionService = SubmissionService(playbook) // small delay because WorkManager does not run work instantly when delay is off private val delay = 500L @@ -44,7 +47,6 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest { LocalData.registrationToken("test token") LocalData.isTestResultNotificationSent(false) mockkObject(LocalData) - mockkObject(SubmissionService) mockkObject(BackgroundWorkScheduler) // do not init Test WorkManager instance again between tests @@ -118,7 +120,6 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest { @Test fun testDiagnosisTestResultRetrievalPeriodicWorkerRetryAndFail() { val past = Date().time - (BackgroundConstants.POLLING_VALIDITY_MAX_DAYS.toLong() - 1).daysToMilliseconds() - coEvery { SubmissionService.asyncRequestTestResult() } throws Exception("test exception") every { LocalData.initialPollingForTestResultTimeStamp() } returns past BackgroundWorkScheduler.startWorkScheduler() @@ -159,7 +160,7 @@ class DiagnosisTestResultRetrievalPeriodicWorkerTest { past: Long, isCancelTest: Boolean = false ) { - coEvery { SubmissionService.asyncRequestTestResult() } returns result + coEvery { submissionService.asyncRequestTestResult(any()) } returns result every { LocalData.initialPollingForTestResultTimeStamp() } returns past BackgroundWorkScheduler.startWorkScheduler() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt index d11cdbfdf..84252ff2d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/Playbook.kt @@ -18,14 +18,14 @@ interface Playbook { suspend fun testResult(registrationToken: String): TestResult + suspend fun submit(data: SubmissionData) + + suspend fun dummy() + data class SubmissionData( val registrationToken: String, val temporaryExposureKeys: List<TemporaryExposureKeyExportOuterClass.TemporaryExposureKey>, val consentToFederation: Boolean, val visistedCountries: List<String> ) - - suspend fun submit(data: SubmissionData) - - suspend fun dummy() } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt index 0961774da..1931b63c3 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/service/submission/SubmissionService.kt @@ -1,80 +1,38 @@ package de.rki.coronawarnapp.service.submission -import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException -import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.playbook.Playbook -import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository -import de.rki.coronawarnapp.util.TimeStamper -import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.formatter.TestResult import de.rki.coronawarnapp.verification.server.VerificationKeyType -import de.rki.coronawarnapp.worker.BackgroundWorkScheduler - -object SubmissionService { +import javax.inject.Inject +class SubmissionService @Inject constructor( private val playbook: Playbook - get() = AppInjector.component.playbook +) { - private val timeStamper: TimeStamper - get() = TimeStamper() + suspend fun asyncRequestTestResult(registrationToken: String): TestResult { + return playbook.testResult(registrationToken) + } - suspend fun asyncRegisterDeviceViaGUID(guid: String): TestResult { + suspend fun asyncRegisterDeviceViaGUID(guid: String): RegistrationData { val (registrationToken, testResult) = playbook.initialRegistration( guid, VerificationKeyType.GUID ) - LocalData.registrationToken(registrationToken) - deleteTestGUID() - SubmissionRepository.updateTestResult(testResult) - LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) - BackgroundNoise.getInstance().scheduleDummyPattern() - return testResult + return RegistrationData(registrationToken, testResult) } - suspend fun asyncRegisterDeviceViaTAN(tan: String) { + suspend fun asyncRegisterDeviceViaTAN(tan: String): RegistrationData { val (registrationToken, testResult) = playbook.initialRegistration( tan, VerificationKeyType.TELETAN ) - LocalData.registrationToken(registrationToken) - deleteTeleTAN() - SubmissionRepository.updateTestResult(testResult) - LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) - BackgroundNoise.getInstance().scheduleDummyPattern() - } - - suspend fun asyncRequestTestResult(): TestResult { - val registrationToken = - LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() - - return playbook.testResult(registrationToken) - } - - fun containsValidGUID(scanResult: String): Boolean { - val scanResult = QRScanResult(scanResult) - return scanResult.isValid + return RegistrationData(registrationToken, testResult) } - fun storeTestGUID(guid: String) = LocalData.testGUID(guid) - - fun deleteTestGUID() { - LocalData.testGUID(null) - } - - fun deleteRegistrationToken() { - LocalData.registrationToken(null) - LocalData.devicePairingSuccessfulTimestamp(0L) - } - - fun submissionSuccessful() { - BackgroundWorkScheduler.stopWorkScheduler() - LocalData.numberOfSuccessfulSubmissions(1) - } - - private fun deleteTeleTAN() { - LocalData.teletan(null) - } + data class RegistrationData( + val registrationToken: String, + val testResult: TestResult + ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/RiskLevelRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/RiskLevelRepository.kt index 6661469b1..559318598 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/RiskLevelRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/RiskLevelRepository.kt @@ -35,8 +35,6 @@ object RiskLevelRepository { /** * Resets the data in the [RiskLevelRepository] * - * @see de.rki.coronawarnapp.util.DataReset - * */ fun reset() { internalRisklevelScore.value = RiskLevelConstants.UNKNOWN_RISK_INITIAL diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt index b601e6d77..21d0216e4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/SubmissionRepository.kt @@ -1,34 +1,55 @@ package de.rki.coronawarnapp.storage +import androidx.annotation.VisibleForTesting import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.asLiveData import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report +import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.service.submission.SubmissionService +import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.Event -import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.formatter.TestResult import de.rki.coronawarnapp.worker.BackgroundWorkScheduler +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import timber.log.Timber import java.util.Date - -object SubmissionRepository { - - private val appScope by lazy { - AppInjector.component.appScope +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubmissionRepository @Inject constructor( + private val submissionSettings: SubmissionSettings, + private val submissionService: SubmissionService, + @AppScope private val scope: CoroutineScope, + private val timeStamper: TimeStamper +) { + + companion object { + fun submissionSuccessful() { + BackgroundWorkScheduler.stopWorkScheduler() + LocalData.numberOfSuccessfulSubmissions(1) + } + fun deleteRegistrationToken() { + LocalData.registrationToken(null) + LocalData.devicePairingSuccessfulTimestamp(0L) + } } - val uiStateStateFlowInternal = MutableStateFlow(ApiRequestState.IDLE) + private val uiStateErrorInternal = MutableLiveData<Event<CwaWebException>>(null) + val uiStateError: LiveData<Event<CwaWebException>> = uiStateErrorInternal + + private val uiStateStateFlowInternal = MutableStateFlow(ApiRequestState.IDLE) val uiStateStateFlow: Flow<ApiRequestState> = uiStateStateFlowInternal - val uiStateState: LiveData<ApiRequestState> = uiStateStateFlow.asLiveData() private val testResultReceivedDateFlowInternal = MutableStateFlow(Date()) val testResultReceivedDateFlow: Flow<Date> = testResultReceivedDateFlowInternal @@ -36,53 +57,33 @@ object SubmissionRepository { private val deviceUIStateFlowInternal = MutableStateFlow(DeviceUIState.UNPAIRED) val deviceUIStateFlow: Flow<DeviceUIState> = deviceUIStateFlowInternal + // to be used by new submission flow screens + val hasGivenConsentToSubmission = submissionSettings.hasGivenConsent.flow + private val testResultFlow = MutableStateFlow<TestResult?>(null) - private suspend fun fetchTestResult(): DeviceUIState = try { - val testResult = SubmissionService.asyncRequestTestResult() - updateTestResult(testResult) - deriveUiState(testResult) - } catch (err: NoRegistrationTokenSetException) { - DeviceUIState.UNPAIRED + fun setTeletan(teletan: String) { + LocalData.teletan(teletan) } - fun updateTestResult(testResult: TestResult) { - this.testResultFlow.value = testResult - - if (testResult == TestResult.POSITIVE) { - LocalData.isAllowedToSubmitDiagnosisKeys(true) - } - - val initialTestResultReceivedTimestamp = LocalData.initialTestResultReceivedTimestamp() - - if (initialTestResultReceivedTimestamp == null) { - val currentTime = System.currentTimeMillis() - LocalData.initialTestResultReceivedTimestamp(currentTime) - testResultReceivedDateFlowInternal.value = Date(currentTime) - if (testResult == TestResult.PENDING) { - BackgroundWorkScheduler.startWorkScheduler() - } - } else { - testResultReceivedDateFlowInternal.value = Date(initialTestResultReceivedTimestamp) - } + fun deleteTestGUID() { + LocalData.testGUID(null) } - private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) { - TestResult.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE - TestResult.POSITIVE -> DeviceUIState.PAIRED_POSITIVE - TestResult.PENDING -> DeviceUIState.PAIRED_NO_RESULT - TestResult.REDEEMED -> DeviceUIState.PAIRED_REDEEMED - TestResult.INVALID -> DeviceUIState.PAIRED_ERROR - null -> DeviceUIState.UNPAIRED + // to be used by new submission flow screens + fun giveConsentToSubmission() { + submissionSettings.hasGivenConsent.update { + true + } } - fun setTeletan(teletan: String) { - LocalData.teletan(teletan) + // to be used by new submission flow screens + fun revokeConsentToSubmission() { + submissionSettings.hasGivenConsent.update { + false + } } - private val uiStateErrorInternal = MutableLiveData<Event<CwaWebException>>(null) - val uiStateError: LiveData<Event<CwaWebException>> = uiStateErrorInternal - // TODO this should be more UI agnostic fun refreshDeviceUIState(refreshTestResult: Boolean = true) { var refresh = refreshTestResult @@ -95,9 +96,9 @@ object SubmissionRepository { } uiStateStateFlowInternal.value = ApiRequestState.STARTED - appScope.launch { + scope.launch { try { - refreshUIState(refresh) + deviceUIStateFlowInternal.value = refreshUIState(refresh) uiStateStateFlowInternal.value = ApiRequestState.SUCCESS } catch (err: CwaWebException) { uiStateErrorInternal.postValue(Event(err)) @@ -108,30 +109,90 @@ object SubmissionRepository { } } - fun reset() { - uiStateStateFlowInternal.value = ApiRequestState.IDLE - deviceUIStateFlowInternal.value = DeviceUIState.UNPAIRED - } - // TODO this should be more UI agnostic - private suspend fun refreshUIState(refreshTestResult: Boolean) { + suspend fun refreshUIState(refreshTestResult: Boolean): DeviceUIState { var uiState = DeviceUIState.UNPAIRED if (LocalData.submissionWasSuccessful()) { uiState = DeviceUIState.SUBMITTED_FINAL } else { - if (LocalData.registrationToken() != null) { + val registrationToken = LocalData.registrationToken() + if (registrationToken != null) { uiState = when { LocalData.isAllowedToSubmitDiagnosisKeys() == true -> { DeviceUIState.PAIRED_POSITIVE } - refreshTestResult -> fetchTestResult() + refreshTestResult -> fetchTestResult(registrationToken) else -> { deriveUiState(testResultFlow.value) } } } } - deviceUIStateFlowInternal.value = uiState + return uiState + } + + suspend fun asyncRegisterDeviceViaGUID(guid: String): TestResult { + val registrationData = submissionService.asyncRegisterDeviceViaGUID(guid) + LocalData.registrationToken(registrationData.registrationToken) + LocalData.testGUID(null) + updateTestResult(registrationData.testResult) + LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) + BackgroundNoise.getInstance().scheduleDummyPattern() + return registrationData.testResult + } + + suspend fun asyncRegisterDeviceViaTAN(tan: String) { + val registrationData = submissionService.asyncRegisterDeviceViaTAN(tan) + LocalData.registrationToken(registrationData.registrationToken) + LocalData.teletan(null) + updateTestResult(registrationData.testResult) + LocalData.devicePairingSuccessfulTimestamp(timeStamper.nowUTC.millis) + BackgroundNoise.getInstance().scheduleDummyPattern() + } + + fun reset() { + uiStateStateFlowInternal.value = ApiRequestState.IDLE + deviceUIStateFlowInternal.value = DeviceUIState.UNPAIRED + revokeConsentToSubmission() + } + + @VisibleForTesting + fun updateTestResult(testResult: TestResult) { + testResultFlow.value = testResult + + if (testResult == TestResult.POSITIVE) { + LocalData.isAllowedToSubmitDiagnosisKeys(true) + } + + val initialTestResultReceivedTimestamp = LocalData.initialTestResultReceivedTimestamp() + + if (initialTestResultReceivedTimestamp == null) { + val currentTime = System.currentTimeMillis() + LocalData.initialTestResultReceivedTimestamp(currentTime) + testResultReceivedDateFlowInternal.value = Date(currentTime) + if (testResult == TestResult.PENDING) { + BackgroundWorkScheduler.startWorkScheduler() + } + } else { + testResultReceivedDateFlowInternal.value = Date(initialTestResultReceivedTimestamp) + } + } + + private suspend fun fetchTestResult(registrationToken: String): DeviceUIState = try { + val testResult = submissionService.asyncRequestTestResult(registrationToken) + updateTestResult(testResult) + deriveUiState(testResult) + } catch (err: NoRegistrationTokenSetException) { + DeviceUIState.UNPAIRED } } + +private fun deriveUiState(testResult: TestResult?): DeviceUIState = when (testResult) { + TestResult.NEGATIVE -> DeviceUIState.PAIRED_NEGATIVE + TestResult.POSITIVE -> DeviceUIState.PAIRED_POSITIVE + TestResult.PENDING -> DeviceUIState.PAIRED_NO_RESULT + TestResult.REDEEMED -> DeviceUIState.PAIRED_REDEEMED + TestResult.INVALID -> DeviceUIState.PAIRED_ERROR + null -> DeviceUIState.UNPAIRED +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt new file mode 100644 index 000000000..8fb727618 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt @@ -0,0 +1,22 @@ +package de.rki.coronawarnapp.submission + +import android.content.Context +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.preferences.createFlowPreference +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SubmissionSettings @Inject constructor( + @AppContext val context: Context +) { + + private val prefs by lazy { + context.getSharedPreferences("submission_localdata", Context.MODE_PRIVATE) + } + + val hasGivenConsent = prefs.createFlowPreference( + key = "key_submission_consent", + defaultValue = false + ) +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt index 2bff73211..9f7350a10 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionTask.kt @@ -4,7 +4,7 @@ import com.google.android.gms.nearby.exposurenotification.TemporaryExposureKey import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.playbook.Playbook import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass -import de.rki.coronawarnapp.service.submission.SubmissionService +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.task.Task import de.rki.coronawarnapp.task.TaskCancellationException import de.rki.coronawarnapp.task.TaskFactory @@ -41,7 +41,7 @@ class SubmissionTask @Inject constructor( .also { checkCancel() } .let { playbook.submit(it) } - SubmissionService.submissionSuccessful() + SubmissionRepository.submissionSuccessful() object : Task.Result {} } catch (error: Exception) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt index ea111127b..5b08c3235 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/HomeFragmentViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.asLiveData import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.notification.TestResultNotificationService import de.rki.coronawarnapp.risk.TimeVariables -import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.TracingRepository @@ -34,7 +33,8 @@ class HomeFragmentViewModel @AssistedInject constructor( private val submissionCardsStateProvider: SubmissionCardsStateProvider, val settingsViewModel: SettingsViewModel, private val tracingRepository: TracingRepository, - private val testResultNotificationService: TestResultNotificationService + private val testResultNotificationService: TestResultNotificationService, + private val submissionRepository: SubmissionRepository ) : CWAViewModel( dispatcherProvider = dispatcherProvider, childViewModels = listOf(settingsViewModel) @@ -100,7 +100,7 @@ class HomeFragmentViewModel @AssistedInject constructor( } fun refreshRequiredData() { - SubmissionRepository.refreshDeviceUIState() + submissionRepository.refreshDeviceUIState() // TODO the ordering here is weird, do we expect these to run in sequence? tracingRepository.refreshRiskLevel() tracingRepository.refreshExposureSummary() @@ -123,11 +123,11 @@ class HomeFragmentViewModel @AssistedInject constructor( } fun deregisterWarningAccepted() { - SubmissionService.deleteTestGUID() - SubmissionService.deleteRegistrationToken() + submissionRepository.deleteTestGUID() + SubmissionRepository.deleteRegistrationToken() LocalData.isAllowedToSubmitDiagnosisKeys(false) LocalData.initialTestResultReceivedTimestamp(0L) - SubmissionRepository.refreshDeviceUIState() + submissionRepository.refreshDeviceUIState() } fun userHasAcknowledgedTheLoweredRiskLevel() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt index 087c1e1f7..0b4e70ab4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/main/home/SubmissionCardsStateProvider.kt @@ -14,11 +14,13 @@ import timber.log.Timber import javax.inject.Inject @Reusable -class SubmissionCardsStateProvider @Inject constructor() { +class SubmissionCardsStateProvider @Inject constructor( + submissionRepository: SubmissionRepository +) { val state: Flow<SubmissionCardState> = combine( - SubmissionRepository.deviceUIStateFlow, - SubmissionRepository.uiStateStateFlow + submissionRepository.deviceUIStateFlow, + submissionRepository.uiStateStateFlow ) { args -> SubmissionCardState( deviceUiState = args[0] as DeviceUIState, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt index a685bab2c..e6412428b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/settings/SettingsResetViewModel.kt @@ -6,6 +6,7 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.nearby.InternalExposureNotificationClient import de.rki.coronawarnapp.notification.TestResultNotificationService +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.ui.SingleLiveEvent import de.rki.coronawarnapp.util.DataReset import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -16,7 +17,8 @@ import de.rki.coronawarnapp.worker.BackgroundWorkScheduler class SettingsResetViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val dataReset: DataReset, - private val testResultNotificationService: TestResultNotificationService + private val testResultNotificationService: TestResultNotificationService, + private val submissionRepository: SubmissionRepository ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val clickEvent: SingleLiveEvent<SettingsEvents> = SingleLiveEvent() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt index 1564dde8f..68b0d6ddc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/fragment/SubmissionDispatcherFragment.kt @@ -79,15 +79,15 @@ class SubmissionDispatcherFragment : Fragment(R.layout.fragment_submission_dispa R.string.submission_dispatcher_qr_privacy_dialog_button_positive, R.string.submission_dispatcher_qr_privacy_dialog_button_negative, true, - { - privacyPermissionIsGranted() + positiveButtonFunction = { + onPrivacyPermissionGranted() } ) DialogHelper.showDialog(cameraPermissionRationaleDialogInstance) } - private fun privacyPermissionIsGranted() { + private fun onPrivacyPermissionGranted() { viewModel.onQRScanPressed() } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt index ec4ade17f..77ea20599 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt @@ -7,8 +7,8 @@ import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report import de.rki.coronawarnapp.service.submission.QRScanResult -import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.ui.submission.ScanStatus import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents @@ -18,7 +18,9 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory import timber.log.Timber -class SubmissionQRCodeScanViewModel @AssistedInject constructor() : +class SubmissionQRCodeScanViewModel @AssistedInject constructor( + private val submissionRepository: SubmissionRepository +) : CWAViewModel() { val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() val showRedeemedTokenWarning = SingleLiveEvent<Unit>() @@ -42,7 +44,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor() : private fun doDeviceRegistration(scanResult: QRScanResult) = launch { try { registrationState.postValue(ApiRequestState.STARTED) - checkTestResult(SubmissionService.asyncRegisterDeviceViaGUID(scanResult.guid!!)) + checkTestResult(submissionRepository.asyncRegisterDeviceViaGUID(scanResult.guid!!)) registrationState.postValue(ApiRequestState.SUCCESS) } catch (err: CwaWebException) { registrationState.postValue(ApiRequestState.FAILED) @@ -73,8 +75,8 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor() : private fun deregisterTestFromDevice() { launch { Timber.d("deregisterTestFromDevice()") - SubmissionService.deleteTestGUID() - SubmissionService.deleteRegistrationToken() + submissionRepository.deleteTestGUID() + SubmissionRepository.deleteRegistrationToken() LocalData.isAllowedToSubmitDiagnosisKeys(false) LocalData.initialTestResultReceivedTimestamp(0L) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt index 28dbe39bc..1a8472ccc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModel.kt @@ -7,7 +7,6 @@ import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.exception.reporting.report -import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ApiRequestState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -19,7 +18,8 @@ import kotlinx.coroutines.flow.map import timber.log.Timber class SubmissionTanViewModel @AssistedInject constructor( - dispatcherProvider: DispatcherProvider + dispatcherProvider: DispatcherProvider, + private val submissionRepository: SubmissionRepository ) : CWAViewModel() { private val currentTan = MutableStateFlow(Tan("")) @@ -47,12 +47,12 @@ class SubmissionTanViewModel @AssistedInject constructor( return } Timber.d("Storing teletan $teletan") - SubmissionRepository.setTeletan(teletan.value) + submissionRepository.setTeletan(teletan.value) launch { try { registrationState.postValue(ApiRequestState.STARTED) - SubmissionService.asyncRegisterDeviceViaTAN(teletan.value) + submissionRepository.asyncRegisterDeviceViaTAN(teletan.value) registrationState.postValue(ApiRequestState.SUCCESS) } catch (err: CwaWebException) { registrationState.postValue(ApiRequestState.FAILED) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt index a1d42d396..651583171 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultFragment.kt @@ -12,7 +12,6 @@ import de.rki.coronawarnapp.databinding.FragmentSubmissionTestResultBinding import de.rki.coronawarnapp.exception.http.CwaClientError import de.rki.coronawarnapp.exception.http.CwaServerError import de.rki.coronawarnapp.exception.http.CwaWebException -import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.DialogHelper import de.rki.coronawarnapp.util.di.AutoInject @@ -141,14 +140,14 @@ class SubmissionTestResultFragment : Fragment(R.layout.fragment_submission_test_ override fun onResume() { super.onResume() binding.submissionTestResultContainer.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT) - SubmissionRepository.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh) + viewModel.refreshDeviceUIState(refreshTestResult = !skipInitialTestResultRefresh) skipInitialTestResultRefresh = false } private fun setButtonOnClickListener() { binding.submissionTestResultButtonPendingRefresh.setOnClickListener { - SubmissionRepository.refreshDeviceUIState() + viewModel.refreshDeviceUIState() binding.submissionTestResultContent.submissionTestResultCard.testResultCard .sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt index 97586ba50..08114cc0e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/testresult/SubmissionTestResultViewModel.kt @@ -6,7 +6,6 @@ import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.http.CwaWebException import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms @@ -26,7 +25,8 @@ import timber.log.Timber class SubmissionTestResultViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, private val enfClient: ENFClient, - private val testResultNotificationService: TestResultNotificationService + private val testResultNotificationService: TestResultNotificationService, + private val submissionRepository: SubmissionRepository ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() @@ -37,9 +37,9 @@ class SubmissionTestResultViewModel @AssistedInject constructor( private val tokenErrorMutex = Mutex() val uiState: LiveData<TestResultUIState> = combineTransform( - SubmissionRepository.uiStateStateFlow, - SubmissionRepository.deviceUIStateFlow, - SubmissionRepository.testResultReceivedDateFlow + submissionRepository.uiStateStateFlow, + submissionRepository.deviceUIStateFlow, + submissionRepository.testResultReceivedDateFlow ) { apiRequestState, deviceUiState, resultDate -> tokenErrorMutex.withLock { @@ -57,11 +57,11 @@ class SubmissionTestResultViewModel @AssistedInject constructor( }.asLiveData(context = dispatcherProvider.Default) suspend fun observeTestResultToSchedulePositiveTestResultReminder() = - SubmissionRepository.deviceUIStateFlow + submissionRepository.deviceUIStateFlow .first { it == DeviceUIState.PAIRED_POSITIVE || it == DeviceUIState.PAIRED_POSITIVE_TELETAN } .also { testResultNotificationService.schedulePositiveTestResultReminder() } - val uiStateError: LiveData<Event<CwaWebException>> = SubmissionRepository.uiStateError + val uiStateError: LiveData<Event<CwaWebException>> = submissionRepository.uiStateError fun onBackPressed() { routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) @@ -94,8 +94,8 @@ class SubmissionTestResultViewModel @AssistedInject constructor( fun deregisterTestFromDevice() { launch { Timber.d("deregisterTestFromDevice()") - SubmissionService.deleteTestGUID() - SubmissionService.deleteRegistrationToken() + submissionRepository.deleteTestGUID() + SubmissionRepository.deleteRegistrationToken() LocalData.isAllowedToSubmitDiagnosisKeys(false) LocalData.initialTestResultReceivedTimestamp(0L) @@ -103,6 +103,10 @@ class SubmissionTestResultViewModel @AssistedInject constructor( } } + fun refreshDeviceUIState(refreshTestResult: Boolean = true) { + submissionRepository.refreshDeviceUIState(refreshTestResult) + } + @AssistedInject.Factory interface Factory : SimpleCWAViewModelFactory<SubmissionTestResultViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt index 4a11a2ed4..2deb4e445 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/warnothers/SubmissionResultPositiveOtherWarningViewModel.kt @@ -7,8 +7,8 @@ import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.notification.TestResultNotificationService -import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.interoperability.InteroperabilityRepository import de.rki.coronawarnapp.submission.SubmissionTask import de.rki.coronawarnapp.submission.Symptoms @@ -115,7 +115,7 @@ class SubmissionResultPositiveOtherWarningViewModel @AssistedInject constructor( private fun submitWithNoDiagnosisKeys() { Timber.d("submitWithNoDiagnosisKeys()") - SubmissionService.submissionSuccessful() + SubmissionRepository.submissionSuccessful() } @AssistedInject.Factory diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt index 6b668e6c4..a80682528 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/viewmodel/SubmissionViewModel.kt @@ -2,11 +2,18 @@ package de.rki.coronawarnapp.ui.viewmodel import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData +import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.util.DeviceUIState import de.rki.coronawarnapp.util.viewmodel.CWAViewModel +import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -class SubmissionViewModel : CWAViewModel() { +class SubmissionViewModel @AssistedInject constructor( + submissionRepository: SubmissionRepository +) : CWAViewModel() { - val deviceUiState: LiveData<DeviceUIState> = SubmissionRepository.deviceUIStateFlow.asLiveData() + val deviceUiState: LiveData<DeviceUIState> = submissionRepository.deviceUIStateFlow.asLiveData() + + @AssistedInject.Factory + interface Factory : SimpleCWAViewModelFactory<SubmissionViewModel> } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt index 86690e225..6e9d54d33 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/DataReset.kt @@ -43,7 +43,8 @@ class DataReset @Inject constructor( @AppContext private val context: Context, private val keyCacheRepository: KeyCacheRepository, private val appConfigProvider: AppConfigProvider, - private val interoperabilityRepository: InteroperabilityRepository + private val interoperabilityRepository: InteroperabilityRepository, + private val submissionRepository: SubmissionRepository ) { private val mutex = Mutex() @@ -61,7 +62,7 @@ class DataReset @Inject constructor( // Reset the current risk level stored in LiveData RiskLevelRepository.reset() // Reset the current states stored in LiveData - SubmissionRepository.reset() + submissionRepository.reset() keyCacheRepository.clear() appConfigProvider.clear() interoperabilityRepository.clear() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt index 60dc57174..dd3bcea83 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt @@ -7,6 +7,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException import de.rki.coronawarnapp.notification.NotificationHelper import de.rki.coronawarnapp.service.submission.SubmissionService import de.rki.coronawarnapp.storage.LocalData @@ -23,7 +24,8 @@ import timber.log.Timber */ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( @Assisted val context: Context, - @Assisted workerParams: WorkerParameters + @Assisted workerParams: WorkerParameters, + private val submissionService: SubmissionService ) : CoroutineWorker(context, workerParams) { companion object { @@ -67,7 +69,8 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor( ) < BackgroundConstants.POLLING_VALIDITY_MAX_DAYS ) { Timber.d(" $id maximum days not exceeded") - val testResult = SubmissionService.asyncRequestTestResult() + val registrationToken = LocalData.registrationToken() ?: throw NoRegistrationTokenSetException() + val testResult = submissionService.asyncRequestTestResult(registrationToken) initiateNotification(testResult) Timber.d(" $id Test Result Notification Initiated") } else { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt index 44a4980d7..ea7a698c4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/HomeFragmentViewModelTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.main.home import android.content.Context import de.rki.coronawarnapp.notification.TestResultNotificationService +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.storage.TracingRepository import de.rki.coronawarnapp.tracing.GeneralTracingStatus import de.rki.coronawarnapp.tracing.GeneralTracingStatus.Status @@ -47,6 +48,7 @@ class HomeFragmentViewModelTest : BaseTest() { @MockK lateinit var submissionCardsStateProvider: SubmissionCardsStateProvider @MockK lateinit var tracingRepository: TracingRepository @MockK lateinit var testResultNotificationService: TestResultNotificationService + @MockK lateinit var submissionRepository: SubmissionRepository @BeforeEach fun setup() { @@ -70,7 +72,8 @@ class HomeFragmentViewModelTest : BaseTest() { tracingCardStateProvider = tracingCardStateProvider, submissionCardsStateProvider = submissionCardsStateProvider, tracingRepository = tracingRepository, - testResultNotificationService = testResultNotificationService + testResultNotificationService = testResultNotificationService, + submissionRepository = submissionRepository ) @Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt index 2eaac14f6..056cb108b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/main/home/SubmissionCardsStateProviderTest.kt @@ -29,11 +29,11 @@ import testhelpers.extensions.InstantExecutorExtension class SubmissionCardsStateProviderTest : BaseTest() { @MockK lateinit var context: Context + @MockK lateinit var submissionRepository: SubmissionRepository @BeforeEach fun setup() { MockKAnnotations.init(this) - mockkObject(SubmissionRepository) mockkObject(LocalData) } @@ -42,12 +42,12 @@ class SubmissionCardsStateProviderTest : BaseTest() { clearAllMocks() } - private fun createInstance() = SubmissionCardsStateProvider() + private fun createInstance() = SubmissionCardsStateProvider(submissionRepository) @Test fun `state is combined correctly`() = runBlockingTest { - every { SubmissionRepository.deviceUIStateFlow } returns flow { emit(DeviceUIState.PAIRED_POSITIVE) } - every { SubmissionRepository.uiStateStateFlow } returns flow { emit(ApiRequestState.SUCCESS) } + every { submissionRepository.deviceUIStateFlow } returns flow { emit(DeviceUIState.PAIRED_POSITIVE) } + every { submissionRepository.uiStateStateFlow } returns flow { emit(ApiRequestState.SUCCESS) } every { LocalData.registrationToken() } returns "token" createInstance().apply { @@ -58,8 +58,8 @@ class SubmissionCardsStateProviderTest : BaseTest() { ) verify { - SubmissionRepository.deviceUIStateFlow - SubmissionRepository.uiStateStateFlow + submissionRepository.deviceUIStateFlow + submissionRepository.uiStateStateFlow LocalData.registrationToken() } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt index 5645c8f49..8fb70df39 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/service/submission/SubmissionServiceTest.kt @@ -1,26 +1,18 @@ package de.rki.coronawarnapp.service.submission -import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException -import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.playbook.Playbook -import de.rki.coronawarnapp.storage.LocalData -import de.rki.coronawarnapp.storage.SubmissionRepository -import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.util.di.AppInjector import de.rki.coronawarnapp.util.di.ApplicationComponent import de.rki.coronawarnapp.util.formatter.TestResult import de.rki.coronawarnapp.verification.server.VerificationKeyType -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations -import io.mockk.Runs import io.mockk.clearAllMocks import io.mockk.coEvery +import io.mockk.coVerify import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.just import io.mockk.mockkObject -import io.mockk.verify import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach @@ -28,36 +20,23 @@ import org.junit.jupiter.api.Test class SubmissionServiceTest { + private val tan = "123456-12345678-1234-4DA7-B166-B86D85475064" private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" private val registrationToken = "asdjnskjfdniuewbheboqudnsojdff" - private val testResult = TestResult.PENDING - @MockK lateinit var backgroundNoise: BackgroundNoise @MockK lateinit var mockPlaybook: Playbook @MockK lateinit var appComponent: ApplicationComponent - private val symptoms = Symptoms(Symptoms.StartOf.OneToTwoWeeksAgo, Symptoms.Indication.POSITIVE) + lateinit var submissionService: SubmissionService @BeforeEach fun setUp() { MockKAnnotations.init(this) - mockkObject(AppInjector) every { AppInjector.component } returns appComponent - every { appComponent.playbook } returns mockPlaybook - mockkObject(BackgroundNoise.Companion) - every { BackgroundNoise.getInstance() } returns backgroundNoise - - mockkObject(LocalData) - - mockkObject(SubmissionRepository) - every { SubmissionRepository.updateTestResult(any()) } just Runs - - every { LocalData.teletan() } returns null - every { LocalData.testGUID() } returns null - every { LocalData.registrationToken() } returns null + submissionService = SubmissionService(mockPlaybook) } @AfterEach @@ -67,87 +46,44 @@ class SubmissionServiceTest { @Test fun registrationWithGUIDSucceeds() { - every { LocalData.testGUID() } returns guid - - every { LocalData.testGUID(any()) } just Runs - every { LocalData.registrationToken(any()) } just Runs - every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs - coEvery { - mockPlaybook.initialRegistration(any(), VerificationKeyType.GUID) + mockPlaybook.initialRegistration(guid, VerificationKeyType.GUID) } returns (registrationToken to TestResult.PENDING) - coEvery { mockPlaybook.testResult(registrationToken) } returns testResult - - every { backgroundNoise.scheduleDummyPattern() } just Runs runBlocking { - SubmissionService.asyncRegisterDeviceViaGUID(guid) + submissionService.asyncRegisterDeviceViaGUID(guid) } - verify(exactly = 1) { - LocalData.registrationToken(registrationToken) - LocalData.devicePairingSuccessfulTimestamp(any()) - LocalData.testGUID(null) - backgroundNoise.scheduleDummyPattern() - SubmissionRepository.updateTestResult(testResult) + coVerify(exactly = 1) { + mockPlaybook.initialRegistration(guid, VerificationKeyType.GUID) } } @Test fun registrationWithTeleTANSucceeds() { - every { LocalData.teletan() } returns guid - - every { LocalData.teletan(any()) } just Runs - every { LocalData.registrationToken(any()) } just Runs - every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs - coEvery { mockPlaybook.initialRegistration(any(), VerificationKeyType.TELETAN) } returns (registrationToken to TestResult.PENDING) - coEvery { mockPlaybook.testResult(registrationToken) } returns testResult - - every { backgroundNoise.scheduleDummyPattern() } just Runs runBlocking { - SubmissionService.asyncRegisterDeviceViaTAN(guid) + submissionService.asyncRegisterDeviceViaTAN(tan) } - verify(exactly = 1) { - LocalData.registrationToken(registrationToken) - LocalData.devicePairingSuccessfulTimestamp(any()) - LocalData.teletan(null) - backgroundNoise.scheduleDummyPattern() - SubmissionRepository.updateTestResult(testResult) - } - } - - @Test - fun requestTestResultWithoutRegistrationTokenFails(): Unit = runBlocking { - shouldThrow<NoRegistrationTokenSetException> { - SubmissionService.asyncRequestTestResult() + coVerify(exactly = 1) { + mockPlaybook.initialRegistration(tan, VerificationKeyType.TELETAN) } } @Test fun requestTestResultSucceeds() { - every { LocalData.registrationToken() } returns registrationToken coEvery { mockPlaybook.testResult(registrationToken) } returns TestResult.NEGATIVE runBlocking { - SubmissionService.asyncRequestTestResult() shouldBe TestResult.NEGATIVE + submissionService.asyncRequestTestResult(registrationToken) shouldBe TestResult.NEGATIVE } - } - - @Test - fun deleteRegistrationTokenSucceeds() { - every { LocalData.registrationToken(null) } just Runs - every { LocalData.devicePairingSuccessfulTimestamp(0L) } just Runs - - SubmissionService.deleteRegistrationToken() - - verify(exactly = 1) { - LocalData.registrationToken(null) - LocalData.devicePairingSuccessfulTimestamp(0L) + coVerify(exactly = 1) { + mockPlaybook.testResult(registrationToken) } } + } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt new file mode 100644 index 000000000..9c84cd632 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt @@ -0,0 +1,115 @@ +package de.rki.coronawarnapp.storage + +import de.rki.coronawarnapp.playbook.BackgroundNoise +import de.rki.coronawarnapp.service.submission.SubmissionService +import de.rki.coronawarnapp.submission.SubmissionSettings +import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.coroutine.AppCoroutineScope +import de.rki.coronawarnapp.util.di.AppInjector +import de.rki.coronawarnapp.util.di.ApplicationComponent +import de.rki.coronawarnapp.util.formatter.TestResult +import de.rki.coronawarnapp.util.security.EncryptedPreferencesFactory +import de.rki.coronawarnapp.util.security.EncryptionErrorResetTool +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockkObject +import io.mockk.verify +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.preferences.mockFlowPreference + +class SubmissionRepositoryTest { + + @MockK lateinit var submissionSettings: SubmissionSettings + @MockK lateinit var submissionService: SubmissionService + + @MockK lateinit var backgroundNoise: BackgroundNoise + @MockK lateinit var appComponent: ApplicationComponent + + @MockK lateinit var encryptedPreferencesFactory: EncryptedPreferencesFactory + @MockK lateinit var encryptionErrorResetTool: EncryptionErrorResetTool + + private val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" + private val tan = "123456-12345678-1234-4DA7-B166-B86D85475064" + private val registrationToken = "asdjnskjfdniuewbheboqudnsojdff" + private val testResult = TestResult.PENDING + private val registrationData = SubmissionService.RegistrationData(registrationToken, testResult) + + lateinit var submissionRepository: SubmissionRepository + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + + mockkObject(AppInjector) + every { AppInjector.component } returns appComponent + every { appComponent.encryptedPreferencesFactory } returns encryptedPreferencesFactory + every { appComponent.errorResetTool } returns encryptionErrorResetTool + + mockkObject(BackgroundNoise.Companion) + every { BackgroundNoise.getInstance() } returns backgroundNoise + every { backgroundNoise.scheduleDummyPattern() } just Runs + + mockkObject(LocalData) + every { LocalData.devicePairingSuccessfulTimestamp(0L) } just Runs + every { LocalData.initialTestResultReceivedTimestamp() } returns 1L + every { LocalData.registrationToken(any()) } just Runs + every { LocalData.devicePairingSuccessfulTimestamp(any()) } just Runs + + every {submissionSettings.hasGivenConsent } returns mockFlowPreference(false) + + val appScope = AppCoroutineScope() + submissionRepository = SubmissionRepository(submissionSettings, submissionService, appScope, TimeStamper()) + } + + @Test + fun deleteRegistrationTokenSucceeds() { + SubmissionRepository.deleteRegistrationToken() + + verify(exactly = 1) { + LocalData.registrationToken(null) + LocalData.devicePairingSuccessfulTimestamp(0L) + } + } + + @Test + fun registrationWithGUIDSucceeds() { + every { LocalData.testGUID(any()) } just Runs + coEvery { submissionService.asyncRegisterDeviceViaGUID(guid) } returns registrationData + + runBlocking { + submissionRepository.asyncRegisterDeviceViaGUID(guid) + } + + verify(exactly = 1) { + LocalData.devicePairingSuccessfulTimestamp(any()) + LocalData.registrationToken(registrationToken) + LocalData.testGUID(null) + backgroundNoise.scheduleDummyPattern() + submissionRepository.updateTestResult(testResult) + } + } + @Test + fun registrationWithTeleTANSucceeds() { + every { LocalData.teletan(any()) } just Runs + coEvery { submissionService.asyncRegisterDeviceViaTAN(tan) } returns registrationData + + runBlocking { + submissionRepository.asyncRegisterDeviceViaTAN(tan) + } + + coVerify(exactly = 1) { + LocalData.devicePairingSuccessfulTimestamp(any()) + LocalData.registrationToken(registrationToken) + LocalData.teletan(null) + backgroundNoise.scheduleDummyPattern() + submissionRepository.updateTestResult(testResult) + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index 05edd8a92..04fe119d4 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.storage.LocalData +import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ScanStatus import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations @@ -21,6 +22,7 @@ import testhelpers.extensions.InstantExecutorExtension class SubmissionQRCodeScanViewModelTest : BaseTest() { @MockK lateinit var backgroundNoise: BackgroundNoise + @MockK lateinit var submissionRepository: SubmissionRepository @BeforeEach fun setUp() { @@ -33,7 +35,7 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { every { BackgroundNoise.getInstance() } returns backgroundNoise } - private fun createViewModel() = SubmissionQRCodeScanViewModel() + private fun createViewModel() = SubmissionQRCodeScanViewModel(submissionRepository) @Test fun scanStatusValid() { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt index ee57585af..4a8649892 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/tan/SubmissionTanViewModelTest.kt @@ -2,11 +2,14 @@ package de.rki.coronawarnapp.ui.submission.tan import de.rki.coronawarnapp.storage.SubmissionRepository import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.verify +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTest @@ -17,10 +20,18 @@ import testhelpers.extensions.InstantExecutorExtension @ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class) class SubmissionTanViewModelTest : BaseTest() { + @MockK lateinit var submissionRepository: SubmissionRepository + private fun createInstance() = SubmissionTanViewModel( - dispatcherProvider = TestDispatcherProvider + dispatcherProvider = TestDispatcherProvider, + submissionRepository = submissionRepository ) + @BeforeEach + fun setUp() { + MockKAnnotations.init(this) + } + @Test fun tanFormatValid() { val viewModel = createInstance() -- GitLab