From 6870e1a68049e97af779aa6f18e0b92a675e39ba Mon Sep 17 00:00:00 2001
From: Matthias Urhahn <matthias.urhahn@sap.com>
Date: Fri, 28 May 2021 15:21:32 +0200
Subject: [PATCH] Extend corona test data structures with digital covid
 certificate related properties (EXPOSUREAPP-7487) (#3309)

* Extend corona test data structures with digital covid certificate related properties.
+Some additional wiring, plumbing and tests for future PRs.

* LINTs

* Adjust TestRegistrationRequest to supply dcc consent and DOB on test registration.

* Remove explicit assignment, defaults are sufficient.

* A few additional unit tests to check defaults.

Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com>
Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com>
---
 .../coronatest/CoronaTestModule.kt            |   4 +-
 .../coronatest/CoronaTestRepository.kt        |  17 +--
 .../coronatest/TestRegistrationRequest.kt     |   4 +
 .../coronatest/qrcode/CoronaTestQRCode.kt     |  11 +-
 .../qrcode/RapidAntigenQrCodeExtractor.kt     |   3 +-
 .../coronatest/tan/CoronaTestTAN.kt           |  17 +--
 .../coronatest/type/CoronaTest.kt             |   9 ++
 .../coronatest/type/CoronaTestProcessor.kt    |   9 +-
 .../coronatest/type/pcr/PCRCoronaTest.kt      |   9 ++
 .../coronatest/type/pcr/PCRProcessor.kt       |  20 ++--
 .../type/rapidantigen/RACoronaTest.kt         |   7 ++
 ...apidAntigenProcessor.kt => RAProcessor.kt} |  29 +++--
 .../submission/SubmissionRepository.kt        |   4 +-
 .../coronatest/CoronaTestRepositoryTest.kt    | 103 ++++++++++++++++++
 .../coronatest/qrcode/CoronaTestQRCodeTest.kt |  30 +++++
 .../storage/CoronaTestStorageTest.kt          |  14 ++-
 .../coronatest/tan/CoronaTestTANTest.kt       |  19 ++++
 .../coronatest/type/pcr/PCRProcessorTest.kt   |  50 +++++----
 .../rapidantigen/RapidAntigenProcessorTest.kt |  43 +++++---
 19 files changed, 304 insertions(+), 98 deletions(-)
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/{RapidAntigenProcessor.kt => RAProcessor.kt} (90%)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
index 3fc92f6ac..b4295fc63 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestModule.kt
@@ -6,7 +6,7 @@ import dagger.multibindings.IntoSet
 import de.rki.coronawarnapp.coronatest.server.VerificationModule
 import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
 import de.rki.coronawarnapp.coronatest.type.pcr.PCRProcessor
-import de.rki.coronawarnapp.coronatest.type.rapidantigen.RapidAntigenProcessor
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RAProcessor
 
 @Module(
     includes = [VerificationModule::class]
@@ -22,6 +22,6 @@ abstract class CoronaTestModule {
     @Binds
     @IntoSet
     abstract fun ratProcessor(
-        processor: RapidAntigenProcessor
+        processor: RAProcessor
     ): CoronaTestProcessor
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
index 8bf90d76a..c0815c4f3 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/CoronaTestRepository.kt
@@ -4,12 +4,9 @@ import de.rki.coronawarnapp.bugreporting.reportProblem
 import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
 import de.rki.coronawarnapp.coronatest.errors.CoronaTestNotFoundException
 import de.rki.coronawarnapp.coronatest.errors.DuplicateCoronaTestException
-import de.rki.coronawarnapp.coronatest.errors.UnknownTestTypeException
 import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestGUID
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
 import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage
-import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
 import de.rki.coronawarnapp.coronatest.type.TestIdentifier
@@ -86,14 +83,10 @@ class CoronaTestRepository @Inject constructor(
                 throw DuplicateCoronaTestException("There is already a test of this type: ${request.type}.")
             }
 
-            val test = when (request) {
-                is CoronaTestQRCode -> processor.create(request)
-                is CoronaTestTAN -> processor.create(request)
-                else -> throw UnknownTestTypeException("Unknown test request: $request")
+            val test = processor.create(request).also {
+                Timber.tag(TAG).i("New test created: %s", it)
             }
 
-            Timber.tag(TAG).i("Adding new test: %s", test)
-
             toMutableMap().apply { this[test.identifier] = test }
         }
 
@@ -198,11 +191,11 @@ class CoronaTestRepository @Inject constructor(
         }
     }
 
-    suspend fun updateConsent(identifier: TestIdentifier, consented: Boolean) {
-        Timber.tag(TAG).i("updateConsent(identifier=%s, consented=%b)", identifier, consented)
+    suspend fun updateSubmissionConsent(identifier: TestIdentifier, consented: Boolean) {
+        Timber.tag(TAG).i("updateSubmissionConsent(identifier=%s, consented=%b)", identifier, consented)
 
         modifyTest(identifier) { processor, before ->
-            processor.updateConsent(before, consented)
+            processor.updateSubmissionConsent(before, consented)
         }
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt
index 27542f0fd..766ca2e57 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/TestRegistrationRequest.kt
@@ -1,8 +1,12 @@
 package de.rki.coronawarnapp.coronatest
 
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import org.joda.time.LocalDate
 
 interface TestRegistrationRequest {
     val type: CoronaTest.Type
     val identifier: String
+    val isDccSupportedbyPoc: Boolean
+    val isDccConsentGiven: Boolean
+    val dateOfBirth: LocalDate?
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
index 9a5c1801a..2e9417b64 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCode.kt
@@ -16,8 +16,13 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest {
     @Parcelize
     data class PCR(
         val qrCodeGUID: CoronaTestGUID,
+        override val isDccConsentGiven: Boolean = false,
+        override val dateOfBirth: LocalDate? = null,
     ) : CoronaTestQRCode() {
 
+        @IgnoredOnParcel
+        override val isDccSupportedbyPoc: Boolean = true
+
         @IgnoredOnParcel
         override val type: CoronaTest.Type = CoronaTest.Type.PCR
 
@@ -36,9 +41,11 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest {
         val createdAt: Instant,
         val firstName: String? = null,
         val lastName: String? = null,
-        val dateOfBirth: LocalDate? = null,
+        override val dateOfBirth: LocalDate? = null,
         val testid: String? = null,
-        val salt: String? = null
+        val salt: String? = null,
+        override val isDccConsentGiven: Boolean = false,
+        override val isDccSupportedbyPoc: Boolean = false,
     ) : CoronaTestQRCode() {
 
         @IgnoredOnParcel
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
index c5881f973..f0bffc613 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/qrcode/RapidAntigenQrCodeExtractor.kt
@@ -40,7 +40,8 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
             lastName = payload.lastName,
             dateOfBirth = payload.dateOfBirth,
             testid = payload.testId,
-            salt = payload.salt
+            salt = payload.salt,
+            isDccSupportedbyPoc = false, // TODO
         )
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt
index 62200ac43..348576e79 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTAN.kt
@@ -5,6 +5,7 @@ import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.Parcelize
+import org.joda.time.LocalDate
 
 sealed class CoronaTestTAN : Parcelable, TestRegistrationRequest {
 
@@ -20,15 +21,17 @@ sealed class CoronaTestTAN : Parcelable, TestRegistrationRequest {
         override val tan: TestTAN,
     ) : CoronaTestTAN() {
 
-        @IgnoredOnParcel override val type: CoronaTest.Type = CoronaTest.Type.PCR
-    }
+        @IgnoredOnParcel
+        override val type: CoronaTest.Type = CoronaTest.Type.PCR
 
-    @Parcelize
-    data class RapidAntigen(
-        override val tan: TestTAN,
-    ) : CoronaTestTAN() {
+        @IgnoredOnParcel
+        override val isDccSupportedbyPoc: Boolean = false
+
+        @IgnoredOnParcel
+        override val isDccConsentGiven: Boolean = false
 
-        @IgnoredOnParcel override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
+        @IgnoredOnParcel
+        override val dateOfBirth: LocalDate? = null
     }
 }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
index dea5510d6..464bb89b5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTest.kt
@@ -38,6 +38,15 @@ interface CoronaTest {
 
     val isResultAvailableNotificationSent: Boolean
 
+    // Is the digital green certificate supported by the point of care that issued the test
+    val isDccSupportedByPoc: Boolean
+
+    // Has the user given consent to us obtaining the DGC
+    val isDccConsentGiven: Boolean
+
+    // Has the corresponding entry been created in the test certificate storage
+    val isDccDataSetCreated: Boolean
+
     enum class Type(val raw: String) {
         @SerializedName("PCR")
         PCR("PCR"),
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt
index 0605ddfc9..68b69445c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestProcessor.kt
@@ -1,15 +1,12 @@
 package de.rki.coronawarnapp.coronatest.type
 
-import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
-import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 
 interface CoronaTestProcessor {
 
     val type: CoronaTest.Type
 
-    suspend fun create(request: CoronaTestQRCode): CoronaTest
-
-    suspend fun create(request: CoronaTestTAN): CoronaTest
+    suspend fun create(request: TestRegistrationRequest): CoronaTest
 
     suspend fun pollServer(test: CoronaTest): CoronaTest
 
@@ -24,7 +21,7 @@ interface CoronaTestProcessor {
 
     suspend fun markViewed(test: CoronaTest): CoronaTest
 
-    suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest
+    suspend fun updateSubmissionConsent(test: CoronaTest, consented: Boolean): CoronaTest
 
     suspend fun updateResultNotification(test: CoronaTest, sent: Boolean): CoronaTest
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
index f9ef2816d..fc85fb4eb 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/PCRCoronaTest.kt
@@ -40,6 +40,12 @@ data class PCRCoronaTest(
 
     @Transient override val isProcessing: Boolean = false,
     @Transient override val lastError: Throwable? = null,
+
+    @SerializedName("isDccConsentGiven")
+    override val isDccConsentGiven: Boolean = false,
+
+    @SerializedName("isDccDataSetCreated")
+    override val isDccDataSetCreated: Boolean = false,
 ) : CoronaTest {
 
     override val type: CoronaTest.Type
@@ -70,6 +76,9 @@ data class PCRCoronaTest(
             else -> throw IllegalArgumentException("Invalid PCR test state $testResult")
         }
 
+    override val isDccSupportedByPoc: Boolean
+        get() = true
+
     enum class State {
         PENDING,
         INVALID,
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 f733872c3..8ee04a424 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
@@ -42,9 +42,14 @@ class PCRProcessor @Inject constructor(
 
     override val type: CoronaTest.Type = CoronaTest.Type.PCR
 
-    override suspend fun create(request: CoronaTestQRCode): PCRCoronaTest {
-        Timber.tag(TAG).d("create(data=%s)", request)
-        request as CoronaTestQRCode.PCR
+    override suspend fun create(request: TestRegistrationRequest): CoronaTest = when (request) {
+        is CoronaTestQRCode.PCR -> createQR(request)
+        is CoronaTestTAN.PCR -> createTAN(request)
+        else -> throw IllegalArgumentException("PCRProcessor: Unknown test request: $request")
+    }
+
+    private suspend fun createQR(request: CoronaTestQRCode.PCR): PCRCoronaTest {
+        Timber.tag(TAG).d("createQR(data=%s)", request)
 
         val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.qrCodeGUID).also {
             Timber.tag(TAG).d("Request %s gave us %s", request, it)
@@ -56,9 +61,8 @@ class PCRProcessor @Inject constructor(
         return createCoronaTest(request, registrationData)
     }
 
-    override suspend fun create(request: CoronaTestTAN): CoronaTest {
-        Timber.tag(TAG).d("create(data=%s)", request)
-        request as CoronaTestTAN.PCR
+    private suspend fun createTAN(request: CoronaTestTAN.PCR): CoronaTest {
+        Timber.tag(TAG).d("createTAN(data=%s)", request)
 
         val registrationData = submissionService.asyncRegisterDeviceViaTAN(request.tan)
 
@@ -202,8 +206,8 @@ class PCRProcessor @Inject constructor(
         return test.copy(isViewed = true)
     }
 
-    override suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest {
-        Timber.tag(TAG).v("updateConsent(test=%s, consented=%b)", test, consented)
+    override suspend fun updateSubmissionConsent(test: CoronaTest, consented: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateSubmissionConsent(test=%s, consented=%b)", test, consented)
         test as PCRCoronaTest
 
         return test.copy(isAdvancedConsentGiven = consented)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt
index 101457efd..44689e8dc 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RACoronaTest.kt
@@ -63,6 +63,13 @@ data class RACoronaTest(
 
     @Transient override val isProcessing: Boolean = false,
     @Transient override val lastError: Throwable? = null,
+
+    @SerializedName("isDccSupportedByPoc")
+    override val isDccSupportedByPoc: Boolean = false,
+    @SerializedName("isDccConsentGiven")
+    override val isDccConsentGiven: Boolean = false,
+    @SerializedName("isDccDataSetCreated")
+    override val isDccDataSetCreated: Boolean = false,
 ) : CoronaTest {
 
     override val type: CoronaTest.Type
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
similarity index 90%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
index e86a84a54..c28253d5f 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RapidAntigenProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/RAProcessor.kt
@@ -1,6 +1,7 @@
 package de.rki.coronawarnapp.coronatest.type.rapidantigen
 
 import dagger.Reusable
+import de.rki.coronawarnapp.coronatest.TestRegistrationRequest
 import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID
@@ -15,7 +16,6 @@ import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED
 import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse
 import de.rki.coronawarnapp.coronatest.server.VerificationServer
-import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN
 import de.rki.coronawarnapp.coronatest.type.CoronaTest
 import de.rki.coronawarnapp.coronatest.type.CoronaTestProcessor
 import de.rki.coronawarnapp.coronatest.type.CoronaTestService
@@ -33,7 +33,7 @@ import timber.log.Timber
 import javax.inject.Inject
 
 @Reusable
-class RapidAntigenProcessor @Inject constructor(
+class RAProcessor @Inject constructor(
     private val timeStamper: TimeStamper,
     private val submissionService: CoronaTestService,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
@@ -42,9 +42,13 @@ class RapidAntigenProcessor @Inject constructor(
 
     override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN
 
-    override suspend fun create(request: CoronaTestQRCode): RACoronaTest {
-        Timber.tag(TAG).d("create(data=%s)", request)
-        request as CoronaTestQRCode.RapidAntigen
+    override suspend fun create(request: TestRegistrationRequest): CoronaTest = when (request) {
+        is CoronaTestQRCode.RapidAntigen -> createQR(request)
+        else -> throw IllegalArgumentException("RAProcessor: Unknown test request: $request")
+    }
+
+    private suspend fun createQR(request: CoronaTestQRCode.RapidAntigen): RACoronaTest {
+        Timber.tag(TAG).d("createQR(data=%s)", request)
 
         analyticsKeySubmissionCollector.reset(type)
         analyticsTestResultCollector.clear(type)
@@ -83,16 +87,11 @@ class RapidAntigenProcessor @Inject constructor(
             firstName = request.firstName,
             lastName = request.lastName,
             dateOfBirth = request.dateOfBirth,
-            sampleCollectedAt = sampleCollectedAt
+            sampleCollectedAt = sampleCollectedAt,
+            isDccSupportedByPoc = request.isDccSupportedbyPoc,
         )
     }
 
-    override suspend fun create(request: CoronaTestTAN): CoronaTest {
-        Timber.tag(TAG).d("create(data=%s)", request)
-        request as CoronaTestTAN.RapidAntigen
-        throw UnsupportedOperationException("There are no TAN based RATs")
-    }
-
     private fun determineReceivedDate(oldTest: RACoronaTest?, newTestResult: CoronaTestResult): Instant? = when {
         oldTest != null && FINAL_STATES.contains(oldTest.testResult) -> oldTest.testResultReceivedAt
         FINAL_STATES.contains(newTestResult) -> timeStamper.nowUTC
@@ -198,8 +197,8 @@ class RapidAntigenProcessor @Inject constructor(
         return test.copy(isViewed = true)
     }
 
-    override suspend fun updateConsent(test: CoronaTest, consented: Boolean): CoronaTest {
-        Timber.tag(TAG).v("updateConsent(test=%s, consented=%b)", test, consented)
+    override suspend fun updateSubmissionConsent(test: CoronaTest, consented: Boolean): CoronaTest {
+        Timber.tag(TAG).v("updateSubmissionConsent(test=%s, consented=%b)", test, consented)
         test as RACoronaTest
 
         return test.copy(isAdvancedConsentGiven = consented)
@@ -236,7 +235,7 @@ private fun CoronaTestResult.toValidatedResult(): CoronaTestResult {
     return if (isValid) {
         this
     } else {
-        Timber.tag(RapidAntigenProcessor.TAG).e("Server returned invalid RapidAntigen testresult $this")
+        Timber.tag(RAProcessor.TAG).e("Server returned invalid RapidAntigen testresult $this")
         RAT_INVALID
     }
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
index 02092bb92..9215b9f36 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionRepository.kt
@@ -51,7 +51,7 @@ class SubmissionRepository @Inject constructor(
             val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
                 ?: throw IllegalStateException("No test of type $type available")
             Timber.tag(TAG).v("giveConsentToSubmission(type=$type): %s", test)
-            coronaTestRepository.updateConsent(identifier = test.identifier, consented = true)
+            coronaTestRepository.updateSubmissionConsent(identifier = test.identifier, consented = true)
         }
     }
 
@@ -62,7 +62,7 @@ class SubmissionRepository @Inject constructor(
             val test = coronaTestRepository.coronaTests.first().singleOrNull { it.type == type }
                 ?: throw IllegalStateException("No test of type $type available")
 
-            coronaTestRepository.updateConsent(identifier = test.identifier, consented = false)
+            coronaTestRepository.updateSubmissionConsent(identifier = test.identifier, consented = false)
         }
     }
 
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
new file mode 100644
index 000000000..267613b4a
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/CoronaTestRepositoryTest.kt
@@ -0,0 +1,103 @@
+package de.rki.coronawarnapp.coronatest
+
+import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository
+import de.rki.coronawarnapp.coronatest.migration.PCRTestMigration
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.storage.CoronaTestStorage
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest
+import de.rki.coronawarnapp.coronatest.type.pcr.PCRProcessor
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest
+import de.rki.coronawarnapp.coronatest.type.rapidantigen.RAProcessor
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import kotlinx.coroutines.CoroutineScope
+import org.joda.time.Instant
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+import testhelpers.TestDispatcherProvider
+import testhelpers.coroutines.runBlockingTest2
+
+class CoronaTestRepositoryTest : BaseTest() {
+    @MockK lateinit var storage: CoronaTestStorage
+    @MockK lateinit var legacyMigration: PCRTestMigration
+    @MockK lateinit var contactDiaryRepository: ContactDiaryRepository
+
+    private var coronaTestsInStorage = mutableSetOf<CoronaTest>()
+
+    private val pcrTest = PCRCoronaTest(
+        identifier = "pcr-identifier",
+        lastUpdatedAt = Instant.EPOCH,
+        registeredAt = Instant.EPOCH,
+        registrationToken = "token",
+        testResult = CoronaTestResult.PCR_REDEEMED,
+    )
+    @MockK lateinit var pcrProcessor: PCRProcessor
+
+    @MockK lateinit var raProcessor: RAProcessor
+    private val raTest = RACoronaTest(
+        identifier = "ra-identifier",
+        lastUpdatedAt = Instant.EPOCH,
+        registeredAt = Instant.EPOCH,
+        registrationToken = "token",
+        testResult = CoronaTestResult.RAT_REDEEMED,
+        testedAt = Instant.EPOCH,
+    )
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        coronaTestsInStorage.add(pcrTest)
+        coronaTestsInStorage.add(raTest)
+
+        legacyMigration.apply {
+            coEvery { startMigration() } returns emptySet()
+            coEvery { finishMigration() } just Runs
+        }
+
+        storage.apply {
+            every { coronaTests = any() } answers {
+                coronaTestsInStorage.clear()
+                coronaTestsInStorage.addAll(arg(0))
+            }
+            every { coronaTests } answers { coronaTestsInStorage }
+        }
+
+        contactDiaryRepository.apply {
+            coEvery { updateTests(any()) } just Runs
+        }
+
+        pcrProcessor.apply {
+            coEvery { updateSubmissionConsent(any(), any()) } answers { arg<PCRCoronaTest>(0) }
+            every { type } returns CoronaTest.Type.PCR
+        }
+
+        raProcessor.apply {
+            coEvery { updateSubmissionConsent(any(), any()) } answers { arg<RACoronaTest>(0) }
+            every { type } returns CoronaTest.Type.RAPID_ANTIGEN
+        }
+    }
+
+    private fun createInstance(scope: CoroutineScope) = CoronaTestRepository(
+        appScope = scope,
+        dispatcherProvider = TestDispatcherProvider(),
+        storage = storage,
+        processors = setOf(pcrProcessor, raProcessor),
+        legacyMigration = legacyMigration,
+        contactDiaryRepository = contactDiaryRepository,
+    )
+
+    @Test
+    fun `give submission consent`() = runBlockingTest2(ignoreActive = true) {
+        createInstance(this).updateSubmissionConsent(pcrTest.identifier, true)
+
+        coVerify { pcrProcessor.updateSubmissionConsent(pcrTest, true) }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt
new file mode 100644
index 000000000..a1d45959d
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/qrcode/CoronaTestQRCodeTest.kt
@@ -0,0 +1,30 @@
+package de.rki.coronawarnapp.coronatest.qrcode
+
+import io.kotest.matchers.shouldBe
+import org.joda.time.Instant
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class CoronaTestQRCodeTest : BaseTest() {
+
+    private val instancePCR = CoronaTestQRCode.PCR("pcr")
+    private val instanceRA = CoronaTestQRCode.RapidAntigen("ra", createdAt = Instant.EPOCH)
+
+    @Test
+    fun `PCR defaults`() {
+        instancePCR.apply {
+            isDccSupportedbyPoc shouldBe true
+            isDccConsentGiven shouldBe false
+            dateOfBirth shouldBe null
+        }
+    }
+
+    @Test
+    fun `RA defaults`() {
+        instanceRA.apply {
+            isDccSupportedbyPoc shouldBe false
+            isDccConsentGiven shouldBe false
+            dateOfBirth shouldBe null
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorageTest.kt
index 100c26a50..331c6f533 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorageTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorageTest.kt
@@ -51,6 +51,8 @@ class CoronaTestStorageTest : BaseTest() {
         testResult = CoronaTestResult.PCR_POSITIVE,
         testResultReceivedAt = Instant.ofEpochMilli(2000),
         lastUpdatedAt = Instant.ofEpochMilli(2001),
+        isDccConsentGiven = true,
+        isDccDataSetCreated = true,
     )
     private val raTest = RACoronaTest(
         identifier = "identifier-ra",
@@ -67,6 +69,9 @@ class CoronaTestStorageTest : BaseTest() {
         dateOfBirth = LocalDate.parse("2021-12-24"),
         testedAt = Instant.ofEpochMilli(3000),
         lastUpdatedAt = Instant.ofEpochMilli(2001),
+        isDccSupportedByPoc = true,
+        isDccConsentGiven = true,
+        isDccDataSetCreated = true,
     )
 
     @Test
@@ -110,7 +115,9 @@ class CoronaTestStorageTest : BaseTest() {
                     "isResultAvailableNotificationSent": false,
                     "testResultReceivedAt": 2000,
                     "testResult": 2,
-                    "lastUpdatedAt": 2001
+                    "lastUpdatedAt": 2001,
+                    "isDccConsentGiven": true,
+                    "isDccDataSetCreated": true
                 }
             ]
         """.toComparableJsonPretty()
@@ -152,7 +159,10 @@ class CoronaTestStorageTest : BaseTest() {
                     "testedAt": 3000,
                     "firstName": "firstname",
                     "lastName": "lastname",
-                    "dateOfBirth": "2021-12-24"
+                    "dateOfBirth": "2021-12-24",
+                    "isDccSupportedByPoc": true,
+                    "isDccConsentGiven": true,
+                    "isDccDataSetCreated": true
                 }
             ]
         """.toComparableJsonPretty()
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt
new file mode 100644
index 000000000..20b1559d6
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/tan/CoronaTestTANTest.kt
@@ -0,0 +1,19 @@
+package de.rki.coronawarnapp.coronatest.tan
+
+import io.kotest.matchers.shouldBe
+import org.junit.jupiter.api.Test
+import testhelpers.BaseTest
+
+class CoronaTestTANTest : BaseTest() {
+
+    private val instancePCR = CoronaTestTAN.PCR("tan")
+
+    @Test
+    fun `dcc is not supported by tans`() {
+        instancePCR.apply {
+            isDccConsentGiven shouldBe false
+            isDccSupportedbyPoc shouldBe false
+            dateOfBirth shouldBe null
+        }
+    }
+}
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 fb402a891..f67ba4c3d 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
@@ -43,6 +43,13 @@ class PCRProcessorTest : BaseTest() {
     @MockK lateinit var analyticsTestResultCollector: AnalyticsTestResultCollector
 
     private val nowUTC = Instant.parse("2021-03-15T05:45:00.000Z")
+    private val defaultTest = PCRCoronaTest(
+        identifier = "identifier",
+        lastUpdatedAt = Instant.EPOCH,
+        registeredAt = nowUTC,
+        registrationToken = "regtoken",
+        testResult = PCR_OR_RAT_PENDING
+    )
 
     @BeforeEach
     fun setup() {
@@ -96,12 +103,8 @@ class PCRProcessorTest : BaseTest() {
     fun `if we receive a pending result 60 days after registration, we map to REDEEMED`() = runBlockingTest {
         val instance = createInstance()
 
-        val pcrTest = PCRCoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
-            registeredAt = nowUTC,
-            registrationToken = "regtoken",
-            testResult = PCR_POSITIVE
+        val pcrTest = defaultTest.copy(
+            testResult = PCR_POSITIVE,
         )
 
         instance.pollServer(pcrTest).testResult shouldBe PCR_OR_RAT_PENDING
@@ -164,12 +167,8 @@ class PCRProcessorTest : BaseTest() {
 
         val instance = createInstance()
 
-        val pcrTest = PCRCoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
-            registeredAt = nowUTC,
-            registrationToken = "regtoken",
-            testResult = PCR_POSITIVE
+        val pcrTest = defaultTest.copy(
+            testResult = PCR_POSITIVE,
         )
 
         values().forEach {
@@ -220,12 +219,9 @@ class PCRProcessorTest : BaseTest() {
 
         val instance = createInstance()
 
-        val pcrTest = PCRCoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
+        val pcrTest = defaultTest.copy(
             registeredAt = nowUTC.minus(Duration.standardDays(22)),
-            registrationToken = "regtoken",
-            testResult = PCR_REDEEMED
+            testResult = PCR_REDEEMED,
         )
 
         // Older than 21 days and already redeemed
@@ -244,12 +240,8 @@ class PCRProcessorTest : BaseTest() {
 
         val instance = createInstance()
 
-        val pcrTest = PCRCoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
-            registeredAt = nowUTC,
-            registrationToken = "regtoken",
-            testResult = PCR_POSITIVE
+        val pcrTest = defaultTest.copy(
+            testResult = PCR_POSITIVE,
         )
 
         // Test is not older than 21 days, we want the error!
@@ -264,4 +256,16 @@ class PCRProcessorTest : BaseTest() {
             lastError shouldBe null
         }
     }
+
+    @Test
+    fun `giving submission consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.updateSubmissionConsent(defaultTest, true) shouldBe defaultTest.copy(
+            isAdvancedConsentGiven = true
+        )
+        instance.updateSubmissionConsent(defaultTest, false) shouldBe defaultTest.copy(
+            isAdvancedConsentGiven = false
+        )
+    }
 }
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 74e69ef1c..a3180a0da 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
@@ -43,6 +43,15 @@ class RapidAntigenProcessorTest : BaseTest() {
 
     private val nowUTC = Instant.parse("2021-03-15T05:45:00.000Z")
 
+    private val defaultTest = RACoronaTest(
+        identifier = "identifier",
+        lastUpdatedAt = Instant.EPOCH,
+        registeredAt = nowUTC,
+        registrationToken = "regtoken",
+        testResult = RAT_PENDING,
+        testedAt = Instant.EPOCH,
+    )
+
     @BeforeEach
     fun setup() {
         MockKAnnotations.init(this)
@@ -85,7 +94,7 @@ class RapidAntigenProcessorTest : BaseTest() {
         }
     }
 
-    fun createInstance() = RapidAntigenProcessor(
+    fun createInstance() = RAProcessor(
         timeStamper = timeStamper,
         submissionService = submissionService,
         analyticsKeySubmissionCollector = analyticsKeySubmissionCollector,
@@ -118,13 +127,8 @@ class RapidAntigenProcessorTest : BaseTest() {
     fun `if we receive a pending result 60 days after registration, we map to REDEEMED`() = runBlockingTest {
         val instance = createInstance()
 
-        val raTest = RACoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
-            registeredAt = nowUTC,
-            registrationToken = "regtoken",
+        val raTest = defaultTest.copy(
             testResult = RAT_POSITIVE,
-            testedAt = Instant.EPOCH,
         )
 
         instance.pollServer(raTest).testResult shouldBe PCR_OR_RAT_PENDING
@@ -189,13 +193,8 @@ class RapidAntigenProcessorTest : BaseTest() {
 
         val instance = createInstance()
 
-        val raTest = RACoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
-            registeredAt = nowUTC,
-            registrationToken = "regtoken",
+        val raTest = defaultTest.copy(
             testResult = RAT_POSITIVE,
-            testedAt = Instant.EPOCH,
         )
 
         values().forEach {
@@ -227,13 +226,9 @@ class RapidAntigenProcessorTest : BaseTest() {
 
         val instance = createInstance()
 
-        val raTest = RACoronaTest(
-            identifier = "identifier",
-            lastUpdatedAt = Instant.EPOCH,
+        val raTest = defaultTest.copy(
             registeredAt = nowUTC.minus(Duration.standardDays(22)),
-            registrationToken = "regtoken",
             testResult = RAT_REDEEMED,
-            testedAt = Instant.EPOCH,
         )
 
         // Older than 21 days and already redeemed
@@ -273,4 +268,16 @@ class RapidAntigenProcessorTest : BaseTest() {
             lastError shouldBe null
         }
     }
+
+    @Test
+    fun `giving submission consent`() = runBlockingTest {
+        val instance = createInstance()
+
+        instance.updateSubmissionConsent(defaultTest, true) shouldBe defaultTest.copy(
+            isAdvancedConsentGiven = true
+        )
+        instance.updateSubmissionConsent(defaultTest, false) shouldBe defaultTest.copy(
+            isAdvancedConsentGiven = false
+        )
+    }
 }
-- 
GitLab