From 3e51ced0bf4a46128e7e276e3637f2f21170dbc2 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Fri, 11 Dec 2020 16:36:45 +0100 Subject: [PATCH] Fix incorrect TRL data being applied to submitted TEKs (EXPOSUREAPP-4260) (#1877) * Explicitly log invalid symptom data. * Unless the symptom flow is successfully completed, we assume Symptoms.NO_INFORMATION. The information is based from one fragment to the next. The stored symptom data is only updated if the last fragment successfully completes. * Unit test for symptom indication. * Update Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt Co-authored-by: chris-cwa <69595386+chris-cwa@users.noreply.github.com> * Update Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt Co-authored-by: chris-cwa <69595386+chris-cwa@users.noreply.github.com> * fixed super-fatal code formatting Co-authored-by: chris-cwa <69595386+chris-cwa@users.noreply.github.com> Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com> Co-authored-by: chris-cwa <chris.cwa.sap@gmail.com> --- .../TransmissionRiskVectorDeterminator.kt | 13 +- .../SubmissionSymptomCalendarFragment.kt | 17 +-- .../SubmissionSymptomCalendarViewModel.kt | 43 ++++-- .../SubmissionSymptomIntroductionFragment.kt | 14 +- .../SubmissionSymptomIntroductionViewModel.kt | 33 +++-- .../viewmodel/SubmissionNavigationEvents.kt | 12 -- .../src/main/res/navigation/nav_graph.xml | 3 + .../SubmissionSymptomCalendarViewModelTest.kt | 114 ++++++++++++++++ ...missionSymptomIntroductionViewModelTest.kt | 124 ++++++++++++++++++ 9 files changed, 308 insertions(+), 65 deletions(-) create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt index 20d4eb3aa..f3b45ad55 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/TransmissionRiskVectorDeterminator.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.submission import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.submission.Symptoms.Indication import de.rki.coronawarnapp.submission.Symptoms.StartOf import de.rki.coronawarnapp.util.TimeAndDateExtensions.ageInDays @@ -47,9 +48,17 @@ class TransmissionRiskVectorDeterminator @Inject constructor( is StartOf.MoreThanTwoWeeks -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5) is StartOf.NoInformation -> intArrayOf(5, 6, 8, 8, 8, 7, 5, 3, 2, 1, 1, 1, 1, 1, 1) is StartOf.OneToTwoWeeksAgo -> intArrayOf(1, 1, 1, 1, 2, 3, 4, 5, 6, 6, 7, 7, 6, 6, 4) - else -> intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + else -> { + IllegalStateException("Positive indication without startDate is not allowed: $symptoms") + .reportProblem( + tag = "TransmissionRiskVectorDeterminator", + info = "Symptoms has an invalid state." + ) + intArrayOf(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + } } Indication.NEGATIVE -> intArrayOf(4, 4, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) Indication.NO_INFORMATION -> intArrayOf(5, 6, 7, 7, 7, 6, 4, 3, 2, 1, 1, 1, 1, 1, 1) - }) + } + ) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt index 16fdcdafa..ad3e5120a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarFragment.kt @@ -4,13 +4,12 @@ import android.content.res.ColorStateList import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment +import androidx.navigation.fragment.navArgs import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomCalendarBinding import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.ui.submission.SubmissionBlockingDialog import de.rki.coronawarnapp.ui.submission.SubmissionCancelDialog -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents.NavigateToMainActivity -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents.NavigateToResultPositiveOtherWarning import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.formatter.formatCalendarBackgroundButtonStyleByState import de.rki.coronawarnapp.util.formatter.formatCalendarButtonStyleByState @@ -24,12 +23,14 @@ import javax.inject.Inject class SubmissionSymptomCalendarFragment : Fragment(R.layout.fragment_submission_symptom_calendar), AutoInject { + private val navArgs by navArgs<SubmissionSymptomCalendarFragmentArgs>() + @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory private val viewModel: SubmissionSymptomCalendarViewModel by cwaViewModelsAssisted( factoryProducer = { viewModelFactory }, constructorCall = { factory, _ -> factory as SubmissionSymptomCalendarViewModel.Factory - factory.create() + factory.create(navArgs.symptomIndication) } ) @@ -54,15 +55,7 @@ class SubmissionSymptomCalendarFragment : Fragment(R.layout.fragment_submission_ } viewModel.routeToScreen.observe2(this) { - when (it) { - is NavigateToResultPositiveOtherWarning -> doNavigate( - SubmissionSymptomCalendarFragmentDirections - .actionSubmissionSymptomCalendarFragmentToSubmissionResultPositiveOtherWarningFragment() - ) - is NavigateToMainActivity -> doNavigate( - SubmissionSymptomCalendarFragmentDirections.actionSubmissionSymptomCalendarFragmentToMainFragment() - ) - } + doNavigate(it) } viewModel.symptomStart.observe2(this) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt index 2e7385ca2..4c99371b8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModel.kt @@ -1,28 +1,30 @@ package de.rki.coronawarnapp.ui.submission.symptoms.calendar import androidx.lifecycle.asLiveData +import androidx.navigation.NavDirections +import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.bugreporting.reportProblem import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.MutableStateFlow import org.joda.time.LocalDate import timber.log.Timber class SubmissionSymptomCalendarViewModel @AssistedInject constructor( + @Assisted val symptomIndication: Symptoms.Indication, dispatcherProvider: DispatcherProvider, private val submissionRepository: SubmissionRepository ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val symptomStart = submissionRepository.currentSymptoms.flow - .map { it?.startOfSymptoms } - .asLiveData(context = dispatcherProvider.Default) + private val symptomStartInternal = MutableStateFlow<Symptoms.StartOf?>(null) + val symptomStart = symptomStartInternal.asLiveData(context = dispatcherProvider.Default) - val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() + val routeToScreen = SingleLiveEvent<NavDirections>() val showCancelDialog = SingleLiveEvent<Unit>() val showUploadDialog = submissionRepository.isSubmissionRunning .asLiveData(context = dispatcherProvider.Default) @@ -48,9 +50,7 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( } private fun updateSymptomStart(startOf: Symptoms.StartOf?) { - submissionRepository.currentSymptoms.update { - (it ?: Symptoms.NO_INFO_GIVEN).copy(startOfSymptoms = startOf) - } + symptomStartInternal.value = startOf } fun onCalendarPreviousClicked() { @@ -58,7 +58,18 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( } fun onDone() { - Timber.d("onDone() clicked on calender screen.") + if (symptomStartInternal.value == null) { + IllegalStateException("Can't finish symptom indication without symptomStart value.") + .reportProblem(tag = TAG, "UI should not allow symptom submission without start date.") + return + } + Timber.tag(TAG).d("onDone() clicked on calender screen.") + submissionRepository.currentSymptoms.update { + Symptoms( + symptomIndication = symptomIndication, + startOfSymptoms = symptomStartInternal.value + ).also { Timber.tag(TAG).v("Symptoms updated to %s", it) } + } performSubmission() } @@ -72,9 +83,11 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( try { submissionRepository.startSubmission() } catch (e: Exception) { - Timber.e(e, "performSubmission() failed.") + Timber.tag(TAG).e(e, "performSubmission() failed.") } finally { - routeToScreen.postValue(SubmissionNavigationEvents.NavigateToMainActivity) + routeToScreen.postValue( + SubmissionSymptomCalendarFragmentDirections.actionSubmissionSymptomCalendarFragmentToMainFragment() + ) } } } @@ -82,6 +95,10 @@ class SubmissionSymptomCalendarViewModel @AssistedInject constructor( @AssistedInject.Factory interface Factory : CWAViewModelFactory<SubmissionSymptomCalendarViewModel> { - fun create(): SubmissionSymptomCalendarViewModel + fun create(symptomIndication: Symptoms.Indication): SubmissionSymptomCalendarViewModel + } + + companion object { + private const val TAG = "SymptomsCalenderVM" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt index 077c1167c..98c2da675 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionFragment.kt @@ -9,7 +9,6 @@ import de.rki.coronawarnapp.databinding.FragmentSubmissionSymptomIntroBinding import de.rki.coronawarnapp.submission.Symptoms import de.rki.coronawarnapp.ui.submission.SubmissionBlockingDialog import de.rki.coronawarnapp.ui.submission.SubmissionCancelDialog -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents import de.rki.coronawarnapp.util.di.AutoInject import de.rki.coronawarnapp.util.formatter.formatBackgroundButtonStyleByState import de.rki.coronawarnapp.util.formatter.formatButtonStyleByState @@ -37,17 +36,8 @@ class SubmissionSymptomIntroductionFragment : Fragment(R.layout.fragment_submiss super.onViewCreated(view, savedInstanceState) uploadDialog = SubmissionBlockingDialog(requireContext()) - viewModel.routeToScreen.observe2(this) { - when (it) { - is SubmissionNavigationEvents.NavigateToSymptomCalendar -> doNavigate( - SubmissionSymptomIntroductionFragmentDirections - .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment() - ) - is SubmissionNavigationEvents.NavigateToMainActivity -> doNavigate( - SubmissionSymptomIntroductionFragmentDirections - .actionSubmissionSymptomIntroductionFragmentToMainFragment() - ) - } + viewModel.navigation.observe2(this) { + doNavigate(it) } viewModel.showCancelDialog.observe2(this) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt index 0ca4d571f..f7c697b4f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModel.kt @@ -1,17 +1,15 @@ package de.rki.coronawarnapp.ui.submission.symptoms.introduction import androidx.lifecycle.asLiveData +import androidx.navigation.NavDirections import com.squareup.inject.assisted.AssistedInject import de.rki.coronawarnapp.storage.SubmissionRepository import de.rki.coronawarnapp.submission.Symptoms -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents.NavigateToMainActivity -import de.rki.coronawarnapp.ui.submission.viewmodel.SubmissionNavigationEvents.NavigateToSymptomCalendar import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.MutableStateFlow import timber.log.Timber class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( @@ -19,11 +17,10 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( private val submissionRepository: SubmissionRepository ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val symptomIndication = submissionRepository.currentSymptoms.flow - .map { it?.symptomIndication } - .asLiveData(context = dispatcherProvider.Default) + private val symptomIndicationInternal = MutableStateFlow<Symptoms.Indication?>(null) + val symptomIndication = symptomIndicationInternal.asLiveData(context = dispatcherProvider.Default) - val routeToScreen: SingleLiveEvent<SubmissionNavigationEvents> = SingleLiveEvent() + val navigation = SingleLiveEvent<NavDirections>() val showCancelDialog = SingleLiveEvent<Unit>() val showUploadDialog = submissionRepository.isSubmissionRunning @@ -31,8 +28,15 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( fun onNextClicked() { launch { - when (submissionRepository.currentSymptoms.value?.symptomIndication) { - Symptoms.Indication.POSITIVE -> routeToScreen.postValue(NavigateToSymptomCalendar) + when (symptomIndicationInternal.value) { + Symptoms.Indication.POSITIVE -> { + navigation.postValue( + SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment( + symptomIndication = Symptoms.Indication.POSITIVE + ) + ) + } Symptoms.Indication.NEGATIVE -> doSubmit() Symptoms.Indication.NO_INFORMATION -> showCancelDialog.postValue(Unit) } @@ -57,9 +61,7 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( private fun updateSymptomIndication(indication: Symptoms.Indication) { Timber.d("updateSymptomIndication(indication=$indication)") - submissionRepository.currentSymptoms.update { - (it ?: Symptoms.NO_INFO_GIVEN).copy(symptomIndication = indication) - } + symptomIndicationInternal.value = indication } fun onCancelConfirmed() { @@ -74,7 +76,10 @@ class SubmissionSymptomIntroductionViewModel @AssistedInject constructor( } catch (e: Exception) { Timber.e(e, "doSubmit() failed.") } finally { - routeToScreen.postValue(NavigateToMainActivity) + navigation.postValue( + SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToMainFragment() + ) } } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt index 3fa560ce1..7bb0e89b2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt @@ -3,23 +3,11 @@ package de.rki.coronawarnapp.ui.submission.viewmodel sealed class SubmissionNavigationEvents { object NavigateToContact : SubmissionNavigationEvents() object NavigateToDispatcher : SubmissionNavigationEvents() - object NavigateToSubmissionDone : SubmissionNavigationEvents() - object NavigateToSubmissionIntro : SubmissionNavigationEvents() object NavigateToQRCodeScan : SubmissionNavigationEvents() object NavigateToDataPrivacy : SubmissionNavigationEvents() - object NavigateToResultPositiveOtherWarning : SubmissionNavigationEvents() - - object NavigateToResultPositiveOtherWarningNoConsent : SubmissionNavigationEvents() - - object NavigateToSymptomSubmission : SubmissionNavigationEvents() - object NavigateToSymptomCalendar : SubmissionNavigationEvents() - object NavigateToSymptomIntroduction : SubmissionNavigationEvents() object NavigateToTAN : SubmissionNavigationEvents() - object NavigateToTestResult : SubmissionNavigationEvents() object NavigateToConsent : SubmissionNavigationEvents() - object NavigateToYourConsent : SubmissionNavigationEvents() object NavigateToMainActivity : SubmissionNavigationEvents() - object ShowCancelDialog : SubmissionNavigationEvents() } diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml index 47e0e681b..77bad0e0c 100644 --- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml +++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml @@ -374,6 +374,9 @@ android:id="@+id/submissionSymptomCalendarFragment" android:name="de.rki.coronawarnapp.ui.submission.symptoms.calendar.SubmissionSymptomCalendarFragment" android:label="SubmissionSymptomCalendarFragment"> + <argument + android:name="symptomIndication" + app:argType="de.rki.coronawarnapp.submission.Symptoms$Indication" /> <action android:id="@+id/action_submissionCalendarFragment_to_submissionSymptomIntroductionFragment" app:destination="@id/submissionSymptomIntroductionFragment" /> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt new file mode 100644 index 000000000..83478fd77 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/calendar/SubmissionSymptomCalendarViewModelTest.kt @@ -0,0 +1,114 @@ +package de.rki.coronawarnapp.ui.submission.symptoms.calendar + +import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms +import de.rki.coronawarnapp.util.preferences.FlowPreference +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.Runs +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerifySequence +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import org.joda.time.LocalDate +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.BaseTest +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension +import testhelpers.preferences.mockFlowPreference + +@ExtendWith(InstantExecutorExtension::class) +class SubmissionSymptomCalendarViewModelTest : BaseTest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + private lateinit var currentSymptoms: FlowPreference<Symptoms?> + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + + currentSymptoms = mockFlowPreference(null) + + every { submissionRepository.isSubmissionRunning } returns flowOf(false) + coEvery { submissionRepository.startSubmission() } just Runs + every { submissionRepository.currentSymptoms } returns currentSymptoms + } + + @AfterEach + fun tearDown() { + clearAllMocks() + } + + private fun createViewModel(indication: Symptoms.Indication = Symptoms.Indication.POSITIVE) = + SubmissionSymptomCalendarViewModel( + symptomIndication = indication, + dispatcherProvider = TestDispatcherProvider, + submissionRepository = submissionRepository + ) + + @Test + fun `symptom indication is not written to settings`() { + createViewModel().apply { + onLastSevenDaysStart() + onOneToTwoWeeksAgoStart() + onMoreThanTwoWeeksStart() + onNoInformationStart() + onDateSelected(LocalDate.now()) + } + + verify(exactly = 0) { submissionRepository.currentSymptoms } + } + + @Test + fun `submission by symptom completion updates symptom data`() { + createViewModel().apply { + onLastSevenDaysStart() + onDone() + } + + coVerifySequence { + submissionRepository.isSubmissionRunning + submissionRepository.currentSymptoms + submissionRepository.startSubmission() + } + + currentSymptoms.value shouldBe Symptoms( + startOfSymptoms = Symptoms.StartOf.LastSevenDays, + symptomIndication = Symptoms.Indication.POSITIVE + ) + } + + @Test + fun `submission by abort does not write any symptom data`() { + createViewModel().onCancelConfirmed() + + coVerifySequence { + submissionRepository.isSubmissionRunning + submissionRepository.startSubmission() + } + } + + @Test + fun `submission shows upload dialog`() { + val uploadStatus = MutableStateFlow(false) + every { submissionRepository.isSubmissionRunning } returns uploadStatus + createViewModel().apply { + showUploadDialog.observeForever { } + showUploadDialog.value shouldBe false + + uploadStatus.value = true + showUploadDialog.value shouldBe true + + uploadStatus.value = false + showUploadDialog.value shouldBe false + } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt new file mode 100644 index 000000000..f54546746 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/symptoms/introduction/SubmissionSymptomIntroductionViewModelTest.kt @@ -0,0 +1,124 @@ +package de.rki.coronawarnapp.ui.submission.symptoms.introduction + +import de.rki.coronawarnapp.storage.SubmissionRepository +import de.rki.coronawarnapp.submission.Symptoms +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.coVerifySequence +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.verify +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import testhelpers.BaseTest +import testhelpers.TestDispatcherProvider +import testhelpers.extensions.InstantExecutorExtension + +@ExtendWith(InstantExecutorExtension::class) +class SubmissionSymptomIntroductionViewModelTest : BaseTest() { + + @MockK lateinit var submissionRepository: SubmissionRepository + + @BeforeEach + fun setUp() { + MockKAnnotations.init(this, relaxed = true) + + every { submissionRepository.isSubmissionRunning } returns flowOf(false) + coEvery { submissionRepository.startSubmission() } just Runs + } + + @AfterEach + fun tearDown() { + clearAllMocks() + } + + private fun createViewModel() = SubmissionSymptomIntroductionViewModel( + dispatcherProvider = TestDispatcherProvider, + submissionRepository = submissionRepository + ) + + @Test + fun `symptom indication is not written to settings`() { + createViewModel().apply { + onPositiveSymptomIndication() + onNegativeSymptomIndication() + onNoInformationSymptomIndication() + onNextClicked() + } + + verify(exactly = 0) { submissionRepository.currentSymptoms } + } + + @Test + fun `positive symptom indication is forwarded using navigation arguments`() { + createViewModel().apply { + onPositiveSymptomIndication() + onNextClicked() + navigation.value shouldBe SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToSubmissionSymptomCalendarFragment( + symptomIndication = Symptoms.Indication.POSITIVE + ) + } + + verify(exactly = 0) { submissionRepository.currentSymptoms } + } + + @Test + fun `negative symptom indication leads to submission`() { + createViewModel().apply { + onNegativeSymptomIndication() + onNextClicked() + navigation.value shouldBe SubmissionSymptomIntroductionFragmentDirections + .actionSubmissionSymptomIntroductionFragmentToMainFragment() + } + + coVerify { submissionRepository.startSubmission() } + } + + @Test + fun `no information symptom indication leads to cancel dialog`() { + createViewModel().apply { + onNoInformationSymptomIndication() + onNextClicked() + navigation.value shouldBe null + showCancelDialog.value shouldBe Unit + } + + verify(exactly = 0) { submissionRepository.currentSymptoms } + } + + @Test + fun `submission by abort does not write any symptom data`() { + createViewModel().onCancelConfirmed() + + coVerifySequence { + submissionRepository.isSubmissionRunning + submissionRepository.startSubmission() + } + } + + @Test + fun `submission shows upload dialog`() { + val uploadStatus = MutableStateFlow(false) + every { submissionRepository.isSubmissionRunning } returns uploadStatus + createViewModel().apply { + showUploadDialog.observeForever { } + showUploadDialog.value shouldBe false + + uploadStatus.value = true + showUploadDialog.value shouldBe true + + uploadStatus.value = false + showUploadDialog.value shouldBe false + } + } +} -- GitLab