From 397d21fbfbc82a753925279b4887c72c65ffdac4 Mon Sep 17 00:00:00 2001
From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com>
Date: Tue, 1 Jun 2021 16:52:10 +0200
Subject: [PATCH] Unit tests PPA (EXPOSUREAPP-6723) (#3313)

* add storage

* adjust donor

* refactor detector

* tests

* fix tests

* fix tests

* klint

* fixes

* fix crash

* fix shared prefs

* fix shared prefs

* detekt

* refactoring

* fix tests

* fix type
klint

* remove unnecessary storage

* use last calculated

* remove change detector

* use last calculated

* fix test

* merge 2.4

* fix naming

* fix getLastChangeToHighRiskPt/Ew

* calculations unit tests

* refactoring

* refactoring

* merge 2.4

* more tests

* rename

* detekt

* merge 2.4

* fix order

Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
---
 .../coronatest/type/pcr/PCRProcessor.kt       |  19 +-
 .../type/rapidantigen/RAProcessor.kt          |   4 +-
 .../analytics/common/PpaDataExtensions.kt     |   3 -
 .../ExposureRiskMetadataDonor.kt              |   2 +-
 .../AnalyticsKeySubmissionStorage.kt          |  12 +-
 .../AnalyticsTestResultCollector.kt           |  27 +-
 .../testresult/AnalyticsTestResultDonor.kt    |   6 -
 .../testresult/AnalyticsTestResultSettings.kt |  20 --
 .../scan/SubmissionQRCodeScanViewModel.kt     |   2 +-
 .../coronatest/type/pcr/PCRProcessorTest.kt   |   4 +-
 .../rapidantigen/RapidAntigenProcessorTest.kt |   4 +-
 .../analytics/common/CalculationsTest.kt      |  73 +++++
 .../AnalyticsKeySubmissionStorageTest.kt      | 176 ++++++++++++
 ....kt => AnalyticsPCRTestResultDonorTest.kt} |  23 +-
 .../AnalyticsRATestResultDonorTest.kt         | 218 ++++++++++++++
 .../AnalyticsTestResultCollectorTest.kt       | 266 ++++++++++++++++++
 .../AnalyticsTestResultDataCollectorTest.kt   | 187 ------------
 .../AnalyticsTestResultSettingsTest.kt        |  91 ++++++
 18 files changed, 876 insertions(+), 261 deletions(-)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorageTest.kt
 rename Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/{AnalyticsPCRTestResultTest.kt => AnalyticsPCRTestResultDonorTest.kt} (88%)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt
 delete mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDataCollectorTest.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt
index 75f83e9dd..a4e0346e2 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessor.kt
@@ -55,6 +55,9 @@ class PCRProcessor @Inject constructor(
     private suspend fun createQR(request: CoronaTestQRCode.PCR): PCRCoronaTest {
         Timber.tag(TAG).d("createQR(data=%s)", request)
 
+        analyticsKeySubmissionCollector.reset(type)
+        analyticsTestResultCollector.clear(type)
+
         val dateOfBirthKey = if (request.isDccConsentGiven && request.dateOfBirth != null) {
             DateOfBirthKey(request.qrCodeGUID, request.dateOfBirth)
         } else null
@@ -69,8 +72,10 @@ class PCRProcessor @Inject constructor(
             Timber.tag(TAG).d("Request %s gave us %s", request, it)
         }
 
-        // This saves received at
-        analyticsTestResultCollector.saveTestResult(registrationData.testResultResponse.coronaTestResult, type)
+        analyticsTestResultCollector.reportTestResultAtRegistration(
+            registrationData.testResultResponse.coronaTestResult,
+            type
+        )
 
         return createCoronaTest(request, registrationData)
     }
@@ -78,6 +83,9 @@ class PCRProcessor @Inject constructor(
     private suspend fun createTAN(request: CoronaTestTAN.PCR): CoronaTest {
         Timber.tag(TAG).d("createTAN(data=%s)", request)
 
+        analyticsKeySubmissionCollector.reset(type)
+        analyticsTestResultCollector.clear(type)
+
         val serverRequest = RegistrationRequest(
             key = request.tan,
             dateOfBirthKey = null,
@@ -98,12 +106,9 @@ class PCRProcessor @Inject constructor(
         response: RegistrationData
     ): PCRCoronaTest {
 
-        analyticsKeySubmissionCollector.reset(type)
-        analyticsTestResultCollector.clear(type)
-
         val testResult = response.testResultResponse.coronaTestResult.let {
             Timber.tag(TAG).v("Raw test result $it")
-            analyticsTestResultCollector.updatePendingTestResultReceivedTime(it, type)
+            analyticsTestResultCollector.reportTestResultReceived(it, type)
             it.toValidatedResult()
         }
 
@@ -148,7 +153,7 @@ class PCRProcessor @Inject constructor(
             val newTestResult = try {
                 submissionService.checkTestResult(test.registrationToken).coronaTestResult.let {
                     Timber.tag(TAG).d("Raw test result was %s", it)
-                    analyticsTestResultCollector.updatePendingTestResultReceivedTime(it, type)
+                    analyticsTestResultCollector.reportTestResultReceived(it, type)
 
                     it.toValidatedResult()
                 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
index 3dcd82592..463d38167 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
@@ -73,8 +73,8 @@ class RAProcessor @Inject constructor(
         val testResult = registrationData.testResultResponse.coronaTestResult.let {
             Timber.tag(TAG).v("Raw test result was %s", it)
             // This saves received at
-            analyticsTestResultCollector.saveTestResult(it, type)
-            analyticsTestResultCollector.updatePendingTestResultReceivedTime(it, type)
+            analyticsTestResultCollector.reportTestResultAtRegistration(it, type)
+            analyticsTestResultCollector.reportTestResultReceived(it, type)
             it.toValidatedResult()
         }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt
index b50296009..c763ed1d7 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensions.kt
@@ -3,7 +3,6 @@ package de.rki.coronawarnapp.datadonation.analytics.common
 import androidx.annotation.StringRes
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
-import de.rki.coronawarnapp.risk.EwRiskLevelResult
 import de.rki.coronawarnapp.risk.RiskState
 import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
 
@@ -68,8 +67,6 @@ val PpaData.PPAFederalState.federalStateShortName: String
         )
     }
 
-fun EwRiskLevelResult.toMetadataRiskLevel(): PpaData.PPARiskLevel = riskState.toMetadataRiskLevel()
-
 fun RiskState.toMetadataRiskLevel(): PpaData.PPARiskLevel =
     when (this) {
         RiskState.INCREASED_RISK -> PpaData.PPARiskLevel.RISK_LEVEL_HIGH
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt
index ac420c74e..21758898d 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposureriskmetadata/ExposureRiskMetadataDonor.kt
@@ -54,7 +54,7 @@ class ExposureRiskMetadataDonor @Inject constructor(
             .lastCalculated
             .ewRiskLevelResult
 
-        val riskLevelEWForMetadata = lastEWRiskResult.toMetadataRiskLevel()
+        val riskLevelEWForMetadata = lastEWRiskResult.riskState.toMetadataRiskLevel()
         val mostRecentDateAtEWRiskLevel = lastEWRiskResult.mostRecentDateAtRiskState?.seconds ?: -1
 
         builder
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt
index 6ef4cfb5f..c530c7af1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorage.kt
@@ -8,11 +8,19 @@ import javax.inject.Inject
 
 class AnalyticsPCRKeySubmissionStorage @Inject constructor(
     @AppContext context: Context
-) : AnalyticsKeySubmissionStorage(context, "") // the original
+) : AnalyticsKeySubmissionStorage(context, sharedPrefKeySuffix) {
+    companion object {
+        const val sharedPrefKeySuffix = "" // the original
+    }
+}
 
 class AnalyticsRAKeySubmissionStorage @Inject constructor(
     @AppContext context: Context
-) : AnalyticsKeySubmissionStorage(context, "_RAT")
+) : AnalyticsKeySubmissionStorage(context, sharedPrefKeySuffix) {
+    companion object {
+        const val sharedPrefKeySuffix = "_RAT"
+    }
+}
 
 open class AnalyticsKeySubmissionStorage(
     val context: Context,
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt
index e991a5135..8e40456a3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt
@@ -8,7 +8,7 @@ import de.rki.coronawarnapp.datadonation.analytics.common.calculateDaysSinceMost
 import de.rki.coronawarnapp.datadonation.analytics.common.getLastChangeToHighEwRiskBefore
 import de.rki.coronawarnapp.datadonation.analytics.common.getLastChangeToHighPtRiskBefore
 import de.rki.coronawarnapp.datadonation.analytics.common.isFinal
-import de.rki.coronawarnapp.datadonation.analytics.common.isPending
+import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel
 import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
 import de.rki.coronawarnapp.risk.RiskState
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
@@ -81,7 +81,7 @@ class AnalyticsTestResultCollector @Inject constructor(
         }
     }
 
-    suspend fun saveTestResult(testResult: CoronaTestResult, type: CoronaTest.Type) {
+    suspend fun reportTestResultAtRegistration(testResult: CoronaTestResult, type: CoronaTest.Type) {
         if (analyticsDisabled) return
 
         val validTestResults = when (type) {
@@ -99,24 +99,31 @@ class AnalyticsTestResultCollector @Inject constructor(
 
         if (testResult !in validTestResults) return // Not interested in other values
 
+        type.settings.testResultAtRegistration.update { testResult }
+
+        if (testResult.isFinal) {
+            type.settings.finalTestResultReceivedAt.update { timeStamper.nowUTC }
+        }
+
         val lastRiskLevel = riskLevelStorage
             .latestAndLastSuccessfulCombinedEwPtRiskLevelResult
             .first()
             .lastSuccessfullyCalculated
 
-        type.settings.saveTestResultDonorDataAtRegistration(testResult, lastRiskLevel)
+        type.settings.ewRiskLevelAtTestRegistration.update {
+            lastRiskLevel.ewRiskLevelResult.riskState.toMetadataRiskLevel()
+        }
+        type.settings.ptRiskLevelAtTestRegistration.update {
+            lastRiskLevel.ptRiskLevelResult.riskState.toMetadataRiskLevel()
+        }
     }
 
-    fun updatePendingTestResultReceivedTime(testResult: CoronaTestResult, type: CoronaTest.Type) {
+    fun reportTestResultReceived(testResult: CoronaTestResult, type: CoronaTest.Type) {
         if (analyticsDisabled) return
-        val shouldUpdate = type.settings.testScannedAfterConsent.value &&
-            type.settings.testResultAtRegistration.value.isPending &&
-            testResult.isFinal
-        if (shouldUpdate) {
+        if (testResult.isFinal) {
             val receivedAt = timeStamper.nowUTC
-            Timber.d("updatePendingTestResultReceivedTime($testResult, $receivedAt")
+            Timber.d("finalTestResultReceivedAt($testResult, $receivedAt")
             type.settings.finalTestResultReceivedAt.update { receivedAt }
-            type.settings.testResultAtRegistration.update { testResult }
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt
index 5978dcc6b..8ec58e107 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt
@@ -30,12 +30,6 @@ abstract class AnalyticsTestResultDonor(
 ) : DonorModule {
 
     override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution {
-        val scannedAfterConsent = testResultSettings.testScannedAfterConsent.value
-        if (!scannedAfterConsent) {
-            Timber.d("Skipping TestResultMetadata donation (scannedAfterConsent=%s)", scannedAfterConsent)
-            return TestResultMetadataNoContribution
-        }
-
         val timestampAtRegistration = testResultSettings.testRegisteredAt.value
         if (timestampAtRegistration == null) {
             Timber.d("Skipping TestResultMetadata donation (timestampAtRegistration is missing)")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt
index 303514cce..c0e24dd75 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt
@@ -2,9 +2,6 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.testresult
 
 import android.content.Context
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
-import de.rki.coronawarnapp.datadonation.analytics.common.isFinal
-import de.rki.coronawarnapp.datadonation.analytics.common.toMetadataRiskLevel
-import de.rki.coronawarnapp.risk.CombinedEwPtRiskLevelResult
 import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
 import de.rki.coronawarnapp.util.TimeStamper
 import de.rki.coronawarnapp.util.di.AppContext
@@ -49,11 +46,6 @@ open class AnalyticsTestResultSettings(
         }
     )
 
-    val testScannedAfterConsent = prefs.createFlowPreference(
-        key = PREFS_KEY_TEST_SCANNED_AFTER_CONSENT + sharedPrefKeySuffix,
-        defaultValue = false
-    )
-
     val ewRiskLevelAtTestRegistration = prefs.createFlowPreference(
         key = PREFS_KEY_RISK_LEVEL_AT_REGISTRATION_EW + sharedPrefKeySuffix,
         reader = { key ->
@@ -124,21 +116,9 @@ open class AnalyticsTestResultSettings(
         defaultValue = -1
     )
 
-    fun saveTestResultDonorDataAtRegistration(testResult: CoronaTestResult, lastResult: CombinedEwPtRiskLevelResult) {
-        testScannedAfterConsent.update { true }
-        testResultAtRegistration.update { testResult }
-        if (testResult.isFinal) {
-            finalTestResultReceivedAt.update { timeStamper.nowUTC }
-        }
-
-        ewRiskLevelAtTestRegistration.update { lastResult.ewRiskLevelResult.toMetadataRiskLevel() }
-        ptRiskLevelAtTestRegistration.update { lastResult.ptRiskLevelResult.riskState.toMetadataRiskLevel() }
-    }
-
     fun clear() = prefs.clearAndNotify()
 
     companion object {
-        private const val PREFS_KEY_TEST_SCANNED_AFTER_CONSENT = "testResultDonor.testScannedAfterConsent"
         private const val PREFS_KEY_TEST_RESULT_AT_REGISTRATION = "testResultDonor.testResultAtRegistration"
 
         private const val PREFS_KEY_RISK_LEVEL_AT_REGISTRATION_EW = "testResultDonor.riskLevelAtRegistration"
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 0f9d04592..cc2f2087c 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
@@ -52,10 +52,10 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
                     )
                 )
             } else {
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, isConsentGiven)
                 if (isConsentGiven) {
                     analyticsKeySubmissionCollector.reportAdvancedConsentGiven(coronaTestQRCode.type)
                 }
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, isConsentGiven)
             }
         } catch (err: InvalidQRCodeException) {
             qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt
index f7d32ec8e..3c89c21c4 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRProcessorTest.kt
@@ -84,8 +84,8 @@ class PCRProcessorTest : BaseTest() {
             coEvery { reportTestRegistered(PCR) } just Runs
         }
         analyticsTestResultCollector.apply {
-            coEvery { updatePendingTestResultReceivedTime(any(), any()) } just Runs
-            coEvery { saveTestResult(any(), PCR) } just Runs
+            coEvery { reportTestResultReceived(any(), any()) } just Runs
+            coEvery { reportTestResultAtRegistration(any(), PCR) } just Runs
             coEvery { reportTestRegistered(PCR) } just Runs
             coEvery { clear(PCR) } just Runs
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt
index 5222c1be5..8f5ba33f3 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessorTest.kt
@@ -87,8 +87,8 @@ class RapidAntigenProcessorTest : BaseTest() {
         }
 
         analyticsTestResultCollector.apply {
-            coEvery { saveTestResult(any(), RAPID_ANTIGEN) } just Runs
-            coEvery { updatePendingTestResultReceivedTime(any(), RAPID_ANTIGEN) } just Runs
+            coEvery { reportTestResultAtRegistration(any(), RAPID_ANTIGEN) } just Runs
+            coEvery { reportTestResultReceived(any(), RAPID_ANTIGEN) } just Runs
             coEvery { reportTestRegistered(RAPID_ANTIGEN) } just Runs
             coEvery { clear(RAPID_ANTIGEN) } just Runs
         }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt
index 21eeafb49..8e59f3a82 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt
@@ -1,8 +1,12 @@
 package de.rki.coronawarnapp.datadonation.analytics.common
 
+import com.google.android.gms.nearby.exposurenotification.ExposureWindow
 import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult
+import de.rki.coronawarnapp.risk.EwRiskLevelResult
 import de.rki.coronawarnapp.risk.RiskState
+import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult
 import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
 import org.joda.time.LocalDate
 import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
@@ -189,4 +193,73 @@ class CalculationsTest : BaseTest() {
         listOf(ptRisk0).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe
             LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant()
     }
+
+    @Test
+    fun `getLastChangeToHighEwRiskBefore works`() {
+        val ewRisk0 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.INCREASED_RISK
+        )
+        val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant()
+        listOf(ewRisk0).getLastChangeToHighEwRiskBefore(registeredAt) shouldBe
+            LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant()
+    }
+
+    @Test
+    fun `getLastChangeToHighEwRiskBefore works 2`() {
+        val risk0 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.LOW_RISK
+        )
+        val risk1 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 20).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.INCREASED_RISK
+        )
+        val risk2 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 21).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.LOW_RISK
+        )
+        val risk3 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.INCREASED_RISK
+        )
+        val risk4 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.CALCULATION_FAILED
+        )
+        val risk5 = TestEwRiskLevelResult(
+            calculatedAt = LocalDate(2021, 3, 24).toDateTimeAtStartOfDay().toInstant(),
+            riskState = RiskState.INCREASED_RISK
+        )
+        val registeredAt = LocalDate(2021, 3, 24).toDateTimeAtStartOfDay().toInstant()
+        listOf(
+            risk0,
+            risk1,
+            risk2,
+            risk3,
+            risk4,
+            risk5
+        ).getLastChangeToHighEwRiskBefore(registeredAt) shouldBe
+            LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant()
+    }
+
+    @Test
+    fun `getLastChangeToHighEwRiskBefore works 3`() {
+        val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant()
+        listOf<EwRiskLevelResult>().getLastChangeToHighEwRiskBefore(registeredAt) shouldBe
+            null
+    }
+}
+
+private data class TestEwRiskLevelResult(
+    override val calculatedAt: Instant,
+    override val riskState: RiskState
+) : EwRiskLevelResult {
+    override val wasSuccessfullyCalculated = true
+    override val failureReason: EwRiskLevelResult.FailureReason?
+        get() = null
+    override val ewAggregatedRiskResult: EwAggregatedRiskResult?
+        get() = null
+    override val exposureWindows: List<ExposureWindow>?
+        get() = null
 }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorageTest.kt
new file mode 100644
index 000000000..0365dcbad
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionStorageTest.kt
@@ -0,0 +1,176 @@
+package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission
+
+import android.content.Context
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.preferences.MockSharedPreferences
+
+class AnalyticsKeySubmissionStorageTest : BaseTest() {
+    @MockK lateinit var context: Context
+    lateinit var preferences: MockSharedPreferences
+    lateinit var pcrStorage: AnalyticsPCRKeySubmissionStorage
+    lateinit var raStorage: AnalyticsRAKeySubmissionStorage
+    private val sharedPrefKey = "analytics_key_submission_localdata"
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+        preferences = MockSharedPreferences()
+        every {
+            context.getSharedPreferences(
+                "$sharedPrefKey${AnalyticsPCRKeySubmissionStorage.sharedPrefKeySuffix}",
+                Context.MODE_PRIVATE
+            )
+        } returns preferences
+        every {
+            context.getSharedPreferences(
+                "$sharedPrefKey${AnalyticsRAKeySubmissionStorage.sharedPrefKeySuffix}",
+                Context.MODE_PRIVATE
+            )
+        } returns preferences
+        pcrStorage = AnalyticsPCRKeySubmissionStorage(
+            context = context
+        )
+        raStorage = AnalyticsRAKeySubmissionStorage(
+            context = context
+        )
+    }
+
+    @AfterEach
+    fun tearDown() {
+        pcrStorage.clear()
+        raStorage.clear()
+    }
+
+    @Test
+    fun dataIsNotMixedPcr() {
+        pcrStorage.submitted.update { true }
+        pcrStorage.submitted.value shouldBe true
+        raStorage.submitted.value shouldBe false
+
+        pcrStorage.submittedWithCheckIns.update { true }
+        pcrStorage.submittedWithCheckIns.value shouldBe true
+        raStorage.submittedWithCheckIns.value shouldBe false
+
+        pcrStorage.submittedAfterCancel.update { true }
+        pcrStorage.submittedAfterCancel.value shouldBe true
+        raStorage.submittedAfterCancel.value shouldBe false
+
+        pcrStorage.submittedAfterSymptomFlow.update { true }
+        pcrStorage.submittedAfterSymptomFlow.value shouldBe true
+        raStorage.submittedAfterSymptomFlow.value shouldBe false
+
+        pcrStorage.submittedInBackground.update { true }
+        pcrStorage.submittedInBackground.value shouldBe true
+        raStorage.submittedInBackground.value shouldBe false
+
+        pcrStorage.submittedAt.update { 2000 }
+        pcrStorage.submittedAt.value shouldBe 2000L
+        raStorage.submittedAt.value shouldBe -1L
+
+        pcrStorage.registeredWithTeleTAN.update { true }
+        pcrStorage.registeredWithTeleTAN.value shouldBe true
+        raStorage.registeredWithTeleTAN.value shouldBe false
+
+        pcrStorage.advancedConsentGiven.update { true }
+        pcrStorage.advancedConsentGiven.value shouldBe true
+        raStorage.advancedConsentGiven.value shouldBe false
+
+        pcrStorage.lastSubmissionFlowScreen.update { 3 }
+        pcrStorage.lastSubmissionFlowScreen.value shouldBe 3
+        raStorage.lastSubmissionFlowScreen.value shouldBe 0
+
+        pcrStorage.testRegisteredAt.update { 1000 }
+        pcrStorage.testRegisteredAt.value shouldBe 1000L
+        raStorage.testRegisteredAt.value shouldBe -1L
+
+        pcrStorage.testResultReceivedAt.update { 3000 }
+        pcrStorage.testResultReceivedAt.value shouldBe 3000L
+        raStorage.testResultReceivedAt.value shouldBe -1L
+
+        pcrStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 3 }
+        pcrStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 3
+        raStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 2 }
+        pcrStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 2
+        raStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ewHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        pcrStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        raStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ptHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        pcrStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        raStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+    }
+
+    @Test
+    fun dataIsNotMixedRa() {
+        raStorage.submitted.update { true }
+        raStorage.submitted.value shouldBe true
+        pcrStorage.submitted.value shouldBe false
+
+        raStorage.submittedWithCheckIns.update { true }
+        raStorage.submittedWithCheckIns.value shouldBe true
+        pcrStorage.submittedWithCheckIns.value shouldBe false
+
+        raStorage.submittedAfterCancel.update { true }
+        raStorage.submittedAfterCancel.value shouldBe true
+        pcrStorage.submittedAfterCancel.value shouldBe false
+
+        raStorage.submittedAfterSymptomFlow.update { true }
+        raStorage.submittedAfterSymptomFlow.value shouldBe true
+        pcrStorage.submittedAfterSymptomFlow.value shouldBe false
+
+        raStorage.submittedInBackground.update { true }
+        raStorage.submittedInBackground.value shouldBe true
+        pcrStorage.submittedInBackground.value shouldBe false
+
+        raStorage.submittedAt.update { 2000 }
+        raStorage.submittedAt.value shouldBe 2000L
+        pcrStorage.submittedAt.value shouldBe -1L
+
+        raStorage.registeredWithTeleTAN.update { true }
+        raStorage.registeredWithTeleTAN.value shouldBe true
+        pcrStorage.registeredWithTeleTAN.value shouldBe false
+
+        raStorage.advancedConsentGiven.update { true }
+        raStorage.advancedConsentGiven.value shouldBe true
+        pcrStorage.advancedConsentGiven.value shouldBe false
+
+        raStorage.testRegisteredAt.update { 1000 }
+        raStorage.testRegisteredAt.value shouldBe 1000L
+        pcrStorage.testRegisteredAt.value shouldBe -1L
+
+        raStorage.testResultReceivedAt.update { 3000 }
+        raStorage.testResultReceivedAt.value shouldBe 3000L
+        pcrStorage.testResultReceivedAt.value shouldBe -1L
+
+        raStorage.lastSubmissionFlowScreen.update { 3 }
+        raStorage.lastSubmissionFlowScreen.value shouldBe 3
+        pcrStorage.lastSubmissionFlowScreen.value shouldBe 0
+
+        raStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 3 }
+        raStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 3
+        pcrStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        raStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 2 }
+        raStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 2
+        pcrStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        raStorage.ewHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        raStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        pcrStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+
+        raStorage.ptHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        raStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        pcrStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt
similarity index 88%
rename from Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultTest.kt
rename to Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt
index cb7200c36..c23987af9 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test
 import testhelpers.BaseTest
 import testhelpers.preferences.mockFlowPreference
 
-class AnalyticsPCRTestResultTest : BaseTest() {
+class AnalyticsPCRTestResultDonorTest : BaseTest() {
     @MockK lateinit var testResultSettings: AnalyticsPCRTestResultSettings
     @MockK lateinit var timeStamper: TimeStamper
 
@@ -57,29 +57,20 @@ class AnalyticsPCRTestResultTest : BaseTest() {
         unmockkAll()
     }
 
-    @Test
-    fun `No donation when user did not allow consent`() = runBlockingTest {
-        every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(false)
-        testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
-    }
-
     @Test
     fun `No donation when timestamp at registration is missing`() = runBlockingTest {
         every { testResultSettings.testRegisteredAt } returns mockFlowPreference(null)
-        every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
         testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
     }
 
     @Test
     fun `No donation when test result is INVALID`() = runBlockingTest {
-        every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
         every { testResultSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_INVALID)
         testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
     }
 
     @Test
     fun `No donation when test result is REDEEMED`() = runBlockingTest {
-        every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
         every { testResultSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_REDEEMED)
         testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
     }
@@ -87,7 +78,6 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `No donation when test result is PENDING and hours isn't greater or equal to config hours`() {
         runBlockingTest {
-            every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultSettings.testResultAtRegistration } returns
                 mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
 
@@ -99,7 +89,6 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `Donation is collected when test result is PENDING and hours is greater or equal to config hours`() {
         runBlockingTest {
-            every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultSettings.testResultAtRegistration } returns
                 mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
             val timeDayBefore = baseTime.minus(Duration.standardDays(1))
@@ -120,10 +109,10 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `Donation is collected when test result is POSITIVE`() {
         runBlockingTest {
-            every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultSettings.testResultAtRegistration } returns
                 mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
-            every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
+            every { testResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(baseTime)
 
             val donation =
                 testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
@@ -140,10 +129,10 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `Donation is collected when test result is NEGATIVE`() {
         runBlockingTest {
-            every { testResultSettings.testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultSettings.testResultAtRegistration } returns
                 mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
-            every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
+            every { testResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(baseTime)
 
             val donation =
                 testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
@@ -160,7 +149,6 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `Scenario 1 LowRisk`() = runBlockingTest {
         with(testResultSettings) {
-            every { testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
             every { finalTestResultReceivedAt } returns mockFlowPreference(
                 Instant.parse("2021-03-20T20:00:00Z")
@@ -186,7 +174,6 @@ class AnalyticsPCRTestResultTest : BaseTest() {
     @Test
     fun `Scenario 2 HighRisk`() = runBlockingTest {
         with(testResultSettings) {
-            every { testScannedAfterConsent } returns mockFlowPreference(true)
             every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
             every { finalTestResultReceivedAt } returns mockFlowPreference(
                 Instant.parse("2021-03-20T20:00:00Z")
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt
new file mode 100644
index 000000000..c2cd808c3
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt
@@ -0,0 +1,218 @@
+package de.rki.coronawarnapp.datadonation.analytics.modules.testresult
+
+import de.rki.coronawarnapp.appconfig.AnalyticsConfig
+import de.rki.coronawarnapp.appconfig.ConfigData
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule
+import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
+import de.rki.coronawarnapp.util.TimeStamper
+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.unmockkAll
+import io.mockk.verify
+import kotlinx.coroutines.test.runBlockingTest
+import org.joda.time.Duration
+import org.joda.time.Instant
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.preferences.mockFlowPreference
+
+class AnalyticsRATestResultDonorTest : BaseTest() {
+    @MockK lateinit var testResultSettings: AnalyticsRATestResultSettings
+    @MockK lateinit var timeStamper: TimeStamper
+
+    private lateinit var testResultDonor: AnalyticsTestResultDonor
+
+    private val baseTime = Instant.ofEpochMilli(101010101)
+
+    @BeforeEach
+    fun setUp() {
+        MockKAnnotations.init(this, true)
+        with(testResultSettings) {
+            every { testRegisteredAt } returns mockFlowPreference(baseTime)
+            every { ewRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+            every { ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns mockFlowPreference(1)
+            every { ewHoursSinceHighRiskWarningAtTestRegistration } returns mockFlowPreference(1)
+            every { ptRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+            every { ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns mockFlowPreference(1)
+            every { ptHoursSinceHighRiskWarningAtTestRegistration } returns mockFlowPreference(1)
+        }
+        every { timeStamper.nowUTC } returns baseTime
+
+        testResultDonor = AnalyticsRATestResultDonor(
+            testResultSettings,
+            timeStamper
+        )
+    }
+
+    @AfterEach
+    fun tearDown() {
+        unmockkAll()
+    }
+
+    @Test
+    fun `No donation when timestamp at registration is missing`() = runBlockingTest {
+        every { testResultSettings.testRegisteredAt } returns mockFlowPreference(null)
+        testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
+    }
+
+    @Test
+    fun `No donation when test result is INVALID`() = runBlockingTest {
+        every { testResultSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_INVALID)
+        testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
+    }
+
+    @Test
+    fun `No donation when test result is REDEEMED`() = runBlockingTest {
+        every { testResultSettings.testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_REDEEMED)
+        testResultDonor.beginDonation(TestRequest) shouldBe AnalyticsTestResultDonor.TestResultMetadataNoContribution
+    }
+
+    @Test
+    fun `No donation when test result is PENDING and hours isn't greater or equal to config hours`() {
+        runBlockingTest {
+            every { testResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
+
+            testResultDonor.beginDonation(TestRequest) shouldBe
+                AnalyticsTestResultDonor.TestResultMetadataNoContribution
+        }
+    }
+
+    @Test
+    fun `Donation is collected when test result is PENDING and hours is greater or equal to config hours`() {
+        runBlockingTest {
+            every { testResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING)
+            val timeDayBefore = baseTime.minus(Duration.standardDays(1))
+            every { testResultSettings.testRegisteredAt } returns mockFlowPreference(timeDayBefore)
+
+            val donation =
+                testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
+            with(donation.testResultMetadata) {
+                riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+                testResult shouldBe PpaData.PPATestResult.TEST_RESULT_PENDING
+                hoursSinceTestRegistration shouldBe 24
+                hoursSinceHighRiskWarningAtTestRegistration shouldBe 1
+                daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 1
+            }
+        }
+    }
+
+    @Test
+    fun `Donation is collected when test result is POSITIVE`() {
+        runBlockingTest {
+            every { testResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
+            every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
+
+            val donation =
+                testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
+            with(donation.testResultMetadata) {
+                riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+                testResult shouldBe PpaData.PPATestResult.TEST_RESULT_POSITIVE
+                hoursSinceTestRegistration shouldBe 0
+                hoursSinceHighRiskWarningAtTestRegistration shouldBe 1
+                daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 1
+            }
+        }
+    }
+
+    @Test
+    fun `Donation is collected when test result is NEGATIVE`() {
+        runBlockingTest {
+            every { testResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
+            every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(baseTime)
+
+            val donation =
+                testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
+            with(donation.testResultMetadata) {
+                riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+                testResult shouldBe PpaData.PPATestResult.TEST_RESULT_NEGATIVE
+                hoursSinceTestRegistration shouldBe 0
+                hoursSinceHighRiskWarningAtTestRegistration shouldBe 1
+                daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 1
+            }
+        }
+    }
+
+    @Test
+    fun `Scenario 1 LowRisk`() = runBlockingTest {
+        with(testResultSettings) {
+            every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_NEGATIVE)
+            every { finalTestResultReceivedAt } returns mockFlowPreference(
+                Instant.parse("2021-03-20T20:00:00Z")
+            )
+            every { ewRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+            every { testRegisteredAt } returns mockFlowPreference(
+                Instant.parse("2021-03-20T00:00:00Z")
+            )
+        }
+        every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z")
+
+        val donation =
+            testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
+        with(donation.testResultMetadata) {
+            testResult shouldBe PpaData.PPATestResult.TEST_RESULT_NEGATIVE
+            hoursSinceTestRegistration shouldBe 20
+            riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+            hoursSinceHighRiskWarningAtTestRegistration shouldBe 1
+            daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 1
+        }
+    }
+
+    @Test
+    fun `Scenario 2 HighRisk`() = runBlockingTest {
+        with(testResultSettings) {
+            every { testResultAtRegistration } returns mockFlowPreference(CoronaTestResult.PCR_POSITIVE)
+            every { finalTestResultReceivedAt } returns mockFlowPreference(
+                Instant.parse("2021-03-20T20:00:00Z")
+            )
+            every { testRegisteredAt } returns mockFlowPreference(
+                Instant.parse("2021-03-20T00:00:00Z")
+            )
+            every { ewRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH)
+        }
+
+        every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z")
+
+        val donation =
+            testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution
+        with(donation.testResultMetadata) {
+            testResult shouldBe PpaData.PPATestResult.TEST_RESULT_POSITIVE
+            hoursSinceTestRegistration shouldBe 20 // hours
+            riskLevelAtTestRegistration shouldBe PpaData.PPARiskLevel.RISK_LEVEL_HIGH
+            hoursSinceHighRiskWarningAtTestRegistration shouldBe 1
+            daysSinceMostRecentDateAtRiskLevelAtTestRegistration shouldBe 1
+        }
+    }
+
+    @Test
+    fun deleteData() = runBlockingTest {
+        every { testResultSettings.clear() } just Runs
+
+        testResultDonor.deleteData()
+
+        verify {
+            testResultSettings.clear()
+        }
+    }
+
+    object TestRequest : DonorModule.Request {
+        override val currentConfig: ConfigData
+            get() = mockk<ConfigData>().apply {
+                every { analytics } returns
+                    mockk<AnalyticsConfig>().apply {
+                        every { hoursSinceTestRegistrationToSubmitTestResultMetadata } returns 20
+                    }
+            }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt
new file mode 100644
index 000000000..1b1a3ce4e
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt
@@ -0,0 +1,266 @@
+package de.rki.coronawarnapp.datadonation.analytics.modules.testresult
+
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_INVALID
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_NEGATIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED
+import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR
+import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN
+import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
+import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult
+import de.rki.coronawarnapp.risk.CombinedEwPtRiskLevelResult
+import de.rki.coronawarnapp.risk.EwRiskLevelResult
+import de.rki.coronawarnapp.risk.LastCombinedRiskResults
+import de.rki.coronawarnapp.risk.RiskState
+import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
+import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
+import de.rki.coronawarnapp.util.TimeStamper
+import io.mockk.Called
+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.verify
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.runBlockingTest
+import org.joda.time.Instant
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.preferences.mockFlowPreference
+
+class AnalyticsTestResultCollectorTest : BaseTest() {
+
+    @MockK lateinit var analyticsSettings: AnalyticsSettings
+    @MockK lateinit var pcrTestResultSettings: AnalyticsPCRTestResultSettings
+    @MockK lateinit var raTestResultSettings: AnalyticsRATestResultSettings
+    @MockK lateinit var riskLevelStorage: RiskLevelStorage
+    @MockK lateinit var timeStamper: TimeStamper
+    @MockK lateinit var combinedResult: CombinedEwPtRiskLevelResult
+    @MockK lateinit var ewRiskLevelResult: EwRiskLevelResult
+    @MockK lateinit var ptRiskLevelResult: PtRiskLevelResult
+
+    private lateinit var analyticsTestResultCollector: AnalyticsTestResultCollector
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        every { timeStamper.nowUTC } returns Instant.parse("2021-03-02T09:57:11+01:00")
+        every { pcrTestResultSettings.clear() } just Runs
+        every { raTestResultSettings.clear() } just Runs
+
+        val lastCombinedResults = LastCombinedRiskResults(combinedResult, combinedResult)
+        every { combinedResult.ewRiskLevelResult } returns ewRiskLevelResult
+        every { combinedResult.ptRiskLevelResult } returns ptRiskLevelResult
+        every { ewRiskLevelResult.riskState } returns RiskState.LOW_RISK
+        every { ptRiskLevelResult.riskState } returns RiskState.LOW_RISK
+        every { riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult } returns
+            flowOf(lastCombinedResults)
+
+        analyticsTestResultCollector = AnalyticsTestResultCollector(
+            analyticsSettings,
+            pcrTestResultSettings,
+            raTestResultSettings,
+            riskLevelStorage,
+            timeStamper,
+        )
+    }
+
+    @Test
+    fun `saveTestResultAnalyticsSettings does not save anything when no user consent`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
+            analyticsTestResultCollector.reportTestResultAtRegistration(PCR_POSITIVE, PCR)
+
+            verify(exactly = 0) {
+                pcrTestResultSettings.testResultAtRegistration
+                raTestResultSettings.testResultAtRegistration
+            }
+
+            analyticsTestResultCollector.reportTestResultAtRegistration(RAT_POSITIVE, RAPID_ANTIGEN)
+
+            verify(exactly = 0) {
+                pcrTestResultSettings.testResultAtRegistration
+                raTestResultSettings.testResultAtRegistration
+            }
+        }
+
+    @Test
+    fun `saveTestResultAtRegistration saves data when user gave consent`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
+
+            // PCR
+            every { pcrTestResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(null)
+            every { pcrTestResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(Instant.EPOCH)
+            every { pcrTestResultSettings.ewRiskLevelAtTestRegistration } returns
+                mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+            every { pcrTestResultSettings.ptRiskLevelAtTestRegistration } returns
+                mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+
+            analyticsTestResultCollector.reportTestResultAtRegistration(PCR_POSITIVE, PCR)
+
+            verify {
+                analyticsSettings.analyticsEnabled
+                pcrTestResultSettings.testResultAtRegistration
+                pcrTestResultSettings.finalTestResultReceivedAt
+                pcrTestResultSettings.ewRiskLevelAtTestRegistration
+                pcrTestResultSettings.ptRiskLevelAtTestRegistration
+            }
+
+            // RAT
+            every { raTestResultSettings.testResultAtRegistration } returns
+                mockFlowPreference(null)
+            every { raTestResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(Instant.EPOCH)
+            every { raTestResultSettings.ewRiskLevelAtTestRegistration } returns
+                mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+            every { raTestResultSettings.ptRiskLevelAtTestRegistration } returns
+                mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_LOW)
+
+            analyticsTestResultCollector.reportTestResultAtRegistration(RAT_POSITIVE, RAPID_ANTIGEN)
+
+            verify {
+                analyticsSettings.analyticsEnabled
+                raTestResultSettings.testResultAtRegistration
+                raTestResultSettings.finalTestResultReceivedAt
+                raTestResultSettings.ptRiskLevelAtTestRegistration
+            }
+        }
+
+    @Test
+    fun `saveTestResultAnalyticsSettings does not save data when TestResult is INVALID`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
+            analyticsTestResultCollector.reportTestResultAtRegistration(PCR_INVALID, PCR)
+            analyticsTestResultCollector.reportTestResultAtRegistration(RAT_INVALID, RAPID_ANTIGEN)
+
+            verify {
+                analyticsSettings.analyticsEnabled wasNot Called
+            }
+        }
+
+    @Test
+    fun `saveTestResultAnalyticsSettings does not save data when TestResult is REDEEMED`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
+            analyticsTestResultCollector.reportTestResultAtRegistration(PCR_REDEEMED, PCR)
+            analyticsTestResultCollector.reportTestResultAtRegistration(RAT_REDEEMED, RAPID_ANTIGEN)
+            verify {
+                analyticsSettings.analyticsEnabled wasNot Called
+            }
+        }
+
+    @Test
+    fun `reportTestResultReceived doesn't update when TestResult isn't POS or NEG`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
+            every { pcrTestResultSettings.testResultAtRegistration } returns mockFlowPreference(
+                PCR_OR_RAT_PENDING
+            )
+            for (testResult in listOf(PCR_REDEEMED, PCR_INVALID, PCR_OR_RAT_PENDING)) {
+                analyticsTestResultCollector.reportTestResultReceived(testResult, PCR)
+
+                verify {
+                    analyticsSettings.analyticsEnabled
+                    pcrTestResultSettings.finalTestResultReceivedAt wasNot Called
+                }
+            }
+
+            every { raTestResultSettings.testResultAtRegistration } returns mockFlowPreference(
+                PCR_OR_RAT_PENDING
+            )
+            for (testResult in listOf(RAT_REDEEMED, RAT_INVALID, PCR_OR_RAT_PENDING)) {
+                analyticsTestResultCollector.reportTestResultReceived(testResult, RAPID_ANTIGEN)
+
+                verify {
+                    analyticsSettings.analyticsEnabled
+                    raTestResultSettings.finalTestResultReceivedAt wasNot Called
+                }
+            }
+        }
+
+    @Test
+    fun `updatePendingTestResultReceivedTime doesn't update when Test is not scanned after consent`() =
+        runBlockingTest {
+            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
+            every { pcrTestResultSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
+            every { pcrTestResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(Instant.parse("2021-03-02T09:57:11+01:00"))
+            analyticsTestResultCollector.reportTestResultReceived(PCR_NEGATIVE, PCR)
+
+            verify {
+                analyticsSettings.analyticsEnabled
+                pcrTestResultSettings.testResultAtRegistration wasNot Called
+                pcrTestResultSettings.finalTestResultReceivedAt wasNot Called
+                pcrTestResultSettings.testResultAtRegistration wasNot Called
+            }
+
+            every { raTestResultSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
+            every { raTestResultSettings.finalTestResultReceivedAt } returns
+                mockFlowPreference(Instant.parse("2021-03-02T09:57:11+01:00"))
+            analyticsTestResultCollector.reportTestResultReceived(RAT_NEGATIVE, RAPID_ANTIGEN)
+
+            verify {
+                analyticsSettings.analyticsEnabled
+                raTestResultSettings.testResultAtRegistration wasNot Called
+                raTestResultSettings.finalTestResultReceivedAt wasNot Called
+                raTestResultSettings.testResultAtRegistration wasNot Called
+            }
+        }
+
+    @Test
+    fun `updatePendingTestResultReceivedTime update when TestResult is POS or NEG`() =
+        runBlockingTest {
+            for (testResult in listOf(PCR_NEGATIVE, PCR_POSITIVE)) {
+                every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
+                every { pcrTestResultSettings.testResultAtRegistration } returns mockFlowPreference(
+                    PCR_OR_RAT_PENDING
+                )
+                every { pcrTestResultSettings.finalTestResultReceivedAt } returns
+                    mockFlowPreference(Instant.EPOCH)
+
+                analyticsTestResultCollector.reportTestResultReceived(testResult, PCR)
+
+                verify {
+                    analyticsSettings.analyticsEnabled
+                    pcrTestResultSettings.finalTestResultReceivedAt
+                }
+            }
+
+            for (testResult in listOf(RAT_NEGATIVE, RAT_POSITIVE)) {
+                every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
+                every { raTestResultSettings.testResultAtRegistration } returns mockFlowPreference(
+                    PCR_OR_RAT_PENDING
+                )
+                every { raTestResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(Instant.EPOCH)
+                analyticsTestResultCollector.reportTestResultReceived(testResult, RAPID_ANTIGEN)
+
+                verify {
+                    analyticsSettings.analyticsEnabled
+                    pcrTestResultSettings.finalTestResultReceivedAt
+                }
+            }
+        }
+
+    @Test
+    fun `clear is clearing saved data`() {
+        analyticsTestResultCollector.clear(PCR)
+        verify {
+            pcrTestResultSettings.clear()
+        }
+        analyticsTestResultCollector.clear(RAPID_ANTIGEN)
+        verify {
+            raTestResultSettings.clear()
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDataCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDataCollectorTest.kt
deleted file mode 100644
index b2318310f..000000000
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDataCollectorTest.kt
+++ /dev/null
@@ -1,187 +0,0 @@
-package de.rki.coronawarnapp.datadonation.analytics.modules.testresult
-
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED
-import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN
-import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings
-import de.rki.coronawarnapp.risk.CombinedEwPtRiskLevelResult
-import de.rki.coronawarnapp.risk.LastCombinedRiskResults
-import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
-import de.rki.coronawarnapp.util.TimeStamper
-import io.mockk.Called
-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.verify
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.runBlockingTest
-import org.joda.time.Instant
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-import testhelpers.BaseTest
-import testhelpers.preferences.mockFlowPreference
-
-class AnalyticsTestResultDataCollectorTest : BaseTest() {
-
-    @MockK lateinit var analyticsSettings: AnalyticsSettings
-    @MockK lateinit var pcrTestResultDonorSettings: AnalyticsPCRTestResultSettings
-    @MockK lateinit var raTestResultDonorSettings: AnalyticsRATestResultSettings
-    @MockK lateinit var riskLevelStorage: RiskLevelStorage
-    @MockK lateinit var timeStamper: TimeStamper
-    @MockK lateinit var combinedResult: CombinedEwPtRiskLevelResult
-
-    private lateinit var analyticsTestResultCollector: AnalyticsTestResultCollector
-
-    @BeforeEach
-    fun setup() {
-        MockKAnnotations.init(this)
-
-        every { timeStamper.nowUTC } returns Instant.parse("2021-03-02T09:57:11+01:00")
-        every { pcrTestResultDonorSettings.clear() } just Runs
-        every { raTestResultDonorSettings.clear() } just Runs
-
-        val lastCombinedResults = LastCombinedRiskResults(combinedResult, combinedResult)
-        every { riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult } returns
-            flowOf(lastCombinedResults)
-
-        analyticsTestResultCollector = AnalyticsTestResultCollector(
-            analyticsSettings,
-            pcrTestResultDonorSettings,
-            raTestResultDonorSettings,
-            riskLevelStorage,
-            timeStamper,
-        )
-    }
-
-    @Test
-    fun `saveTestResultAnalyticsSettings does not save anything when no user consent`() =
-        runBlockingTest {
-            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            analyticsTestResultCollector.saveTestResult(PCR_POSITIVE, PCR)
-
-            verify(exactly = 0) {
-                pcrTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
-                raTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
-            }
-        }
-
-    @Test
-    fun `saveTestResultAnalyticsSettings saves data when user gave consent`() =
-        runBlockingTest {
-            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
-            every { pcrTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any()) } just Runs
-            every { raTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any()) } just Runs
-            analyticsTestResultCollector.saveTestResult(PCR_POSITIVE, PCR)
-
-            verify(exactly = 1) {
-                pcrTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
-            }
-
-            analyticsTestResultCollector.saveTestResult(RAT_POSITIVE, RAPID_ANTIGEN)
-
-            verify(exactly = 1) {
-                raTestResultDonorSettings.saveTestResultDonorDataAtRegistration(any(), any())
-            }
-        }
-
-    @Test
-    fun `saveTestResultAnalyticsSettings does not save data when TestResult is INVALID`() =
-        runBlockingTest {
-            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            analyticsTestResultCollector.saveTestResult(PCR_INVALID, PCR)
-
-            verify {
-                analyticsSettings.analyticsEnabled wasNot Called
-            }
-        }
-
-    @Test
-    fun `saveTestResultAnalyticsSettings does not save data when TestResult is REDEEMED`() =
-        runBlockingTest {
-            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(false)
-            analyticsTestResultCollector.saveTestResult(PCR_REDEEMED, PCR)
-
-            verify {
-                analyticsSettings.analyticsEnabled wasNot Called
-            }
-        }
-
-    @Test
-    fun `updatePendingTestResultReceivedTime doesn't update when TestResult isn't POS or NEG`() =
-        runBlockingTest {
-            for (testResult in listOf(PCR_REDEEMED, PCR_INVALID, PCR_OR_RAT_PENDING)) {
-                every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
-                every { pcrTestResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-                every { pcrTestResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(
-                    PCR_OR_RAT_PENDING
-                )
-                analyticsTestResultCollector.updatePendingTestResultReceivedTime(testResult, PCR)
-
-                verify {
-                    analyticsSettings.analyticsEnabled
-                    pcrTestResultDonorSettings.testScannedAfterConsent
-                    pcrTestResultDonorSettings.testResultAtRegistration
-                    pcrTestResultDonorSettings.finalTestResultReceivedAt wasNot Called
-                    pcrTestResultDonorSettings.testResultAtRegistration wasNot Called
-                }
-            }
-        }
-
-    @Test
-    fun `updatePendingTestResultReceivedTime doesn't update when Test is not scanned after consent`() =
-        runBlockingTest {
-            every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
-            every { pcrTestResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(false)
-            every { pcrTestResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(PCR_OR_RAT_PENDING)
-            analyticsTestResultCollector.updatePendingTestResultReceivedTime(PCR_NEGATIVE, PCR)
-
-            verify {
-                analyticsSettings.analyticsEnabled
-                pcrTestResultDonorSettings.testScannedAfterConsent
-                pcrTestResultDonorSettings.testResultAtRegistration wasNot Called
-                pcrTestResultDonorSettings.finalTestResultReceivedAt wasNot Called
-                pcrTestResultDonorSettings.testResultAtRegistration wasNot Called
-            }
-        }
-
-    @Test
-    fun `updatePendingTestResultReceivedTime update when TestResult is POS or NEG`() =
-        runBlockingTest {
-            for (testResult in listOf(PCR_NEGATIVE, PCR_POSITIVE)) {
-                every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true)
-                every { pcrTestResultDonorSettings.testScannedAfterConsent } returns mockFlowPreference(true)
-                every { pcrTestResultDonorSettings.testResultAtRegistration } returns mockFlowPreference(
-                    PCR_OR_RAT_PENDING
-                )
-                every { pcrTestResultDonorSettings.finalTestResultReceivedAt } returns mockFlowPreference(Instant.EPOCH)
-                analyticsTestResultCollector.updatePendingTestResultReceivedTime(testResult, PCR)
-
-                verify {
-                    analyticsSettings.analyticsEnabled
-                    pcrTestResultDonorSettings.testScannedAfterConsent
-                    pcrTestResultDonorSettings.testResultAtRegistration
-                    pcrTestResultDonorSettings.finalTestResultReceivedAt
-                    pcrTestResultDonorSettings.testResultAtRegistration
-                }
-            }
-        }
-
-    @Test
-    fun `clear is clearing saved data`() {
-        analyticsTestResultCollector.clear(PCR)
-        verify {
-            pcrTestResultDonorSettings.clear()
-        }
-        analyticsTestResultCollector.clear(RAPID_ANTIGEN)
-        verify {
-            raTestResultDonorSettings.clear()
-        }
-    }
-}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt
new file mode 100644
index 000000000..3408ac25f
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt
@@ -0,0 +1,91 @@
+package de.rki.coronawarnapp.datadonation.analytics.modules.testresult
+
+import android.content.Context
+import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData
+import de.rki.coronawarnapp.util.TimeStamper
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import org.joda.time.Instant
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.preferences.MockSharedPreferences
+
+class AnalyticsTestResultSettingsTest : BaseTest() {
+    @MockK lateinit var context: Context
+    @MockK lateinit var timeStamper: TimeStamper
+    lateinit var preferences: MockSharedPreferences
+    lateinit var pcrStorage: AnalyticsPCRTestResultSettings
+    lateinit var raStorage: AnalyticsRATestResultSettings
+    private val sharedPrefKey = "analytics_testResultDonor"
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+        preferences = MockSharedPreferences()
+        every {
+            context.getSharedPreferences(
+                sharedPrefKey,
+                Context.MODE_PRIVATE
+            )
+        } returns preferences
+        every {
+            context.getSharedPreferences(
+                sharedPrefKey + "_RAT",
+                Context.MODE_PRIVATE
+            )
+        } returns preferences
+        pcrStorage = AnalyticsPCRTestResultSettings(
+            context = context,
+            timeStamper = timeStamper
+        )
+        raStorage = AnalyticsRATestResultSettings(
+            context = context,
+            timeStamper = timeStamper
+        )
+    }
+
+    @AfterEach
+    fun tearDown() {
+        pcrStorage.clear()
+        raStorage.clear()
+    }
+
+    @Test
+    fun dataIsNotMixedPcr() {
+        pcrStorage.testRegisteredAt.update { Instant.ofEpochMilli(1000) }
+        pcrStorage.testRegisteredAt.value shouldBe Instant.ofEpochMilli(1000)
+        raStorage.testRegisteredAt.value shouldBe null
+
+        pcrStorage.finalTestResultReceivedAt.update { Instant.ofEpochMilli(3000) }
+        pcrStorage.finalTestResultReceivedAt.value shouldBe Instant.ofEpochMilli(3000)
+        raStorage.finalTestResultReceivedAt.value shouldBe null
+
+        pcrStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 3 }
+        pcrStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 3
+        raStorage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { 2 }
+        pcrStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe 2
+        raStorage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ewHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        pcrStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        raStorage.ewHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ptHoursSinceHighRiskWarningAtTestRegistration.update { 10 }
+        pcrStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe 10
+        raStorage.ptHoursSinceHighRiskWarningAtTestRegistration.value shouldBe -1
+
+        pcrStorage.ewRiskLevelAtTestRegistration.update { PpaData.PPARiskLevel.RISK_LEVEL_HIGH }
+        pcrStorage.ewRiskLevelAtTestRegistration.value shouldBe PpaData.PPARiskLevel.RISK_LEVEL_HIGH
+        raStorage.ewRiskLevelAtTestRegistration.value shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+
+        pcrStorage.ptRiskLevelAtTestRegistration.update { PpaData.PPARiskLevel.RISK_LEVEL_HIGH }
+        pcrStorage.ptRiskLevelAtTestRegistration.value shouldBe PpaData.PPARiskLevel.RISK_LEVEL_HIGH
+        raStorage.ptRiskLevelAtTestRegistration.value shouldBe PpaData.PPARiskLevel.RISK_LEVEL_LOW
+    }
+}
-- 
GitLab