From e5da9ee875050dee2fee61dd0a95142e418902c1 Mon Sep 17 00:00:00 2001
From: Mohamed Metwalli <mohamed.metwalli@sap.com>
Date: Wed, 2 Jun 2021 15:18:46 +0200
Subject: [PATCH] Request DCC Screen (EXPOSUREAPP-7549, 7553) (#3343)

* Add dgc

* Parse value

* Refactoring

* Prase Dgc

* Refactoring

* Register in DCC

* Open privacy

* Analytics

* Lint

* Fix test

* Wire Screen

* Refactoring

* Connect Deletion warning path

* Fix assisted inject

* Create RequestCovidCertificateViewModelTest.kt

* Update RequestCovidCertificateViewModelTest.kt
---
 .../GreenCertificateTestFragment.kt           |  10 -
 .../fragment_test_green_certificate.xml       |  12 -
 .../coronatest/TestRegistrationRequest.kt     |   2 +-
 .../coronatest/qrcode/CoronaTestQRCode.kt     |  10 +-
 .../qrcode/RapidAntigenQrCodeExtractor.kt     |  19 +-
 .../coronatest/tan/CoronaTestTAN.kt           |   2 +-
 .../coronatest/type/CoronaTest.kt             |   2 +
 .../coronatest/type/pcr/PCRCoronaTest.kt      |   3 +
 .../type/rapidantigen/RACoronaTest.kt         |   3 +
 .../type/rapidantigen/RAProcessor.kt          |   2 +-
 .../SubmissionDeletionWarningViewModel.kt     |  63 ++--
 .../RequestCovidCertificateFragment.kt        | 189 +++++++++++
 ... RequestCovidCertificateFragmentModule.kt} |   6 +-
 .../RequestCovidCertificateViewModel.kt       |  97 ++++++
 .../greencertificate/RequestDccNavEvent.kt    |   6 +
 .../RequestGreenCertificateFragment.kt        |  74 -----
 .../RequestGreenCertificateViewModel.kt       |  31 --
 .../QrCodeRegistrationStateProcessor.kt       |   9 +-
 .../scan/SubmissionQRCodeScanFragment.kt      |   7 +-
 .../scan/SubmissionQRCodeScanViewModel.kt     |  44 ++-
 .../viewmodel/SubmissionFragmentModule.kt     |   8 +-
 .../viewmodel/SubmissionNavigationEvents.kt   |   6 +-
 ...=> fragment_request_covid_certificate.xml} |  15 +-
 .../src/main/res/navigation/nav_graph.xml     |  41 ++-
 .../coronatest/qrcode/CoronaTestQRCodeTest.kt |   6 +-
 .../coronatest/tan/CoronaTestTANTest.kt       |   2 +-
 .../RequestCovidCertificateViewModelTest.kt   | 304 ++++++++++++++++++
 .../scan/SubmissionQRCodeScanViewModelTest.kt |  96 ++++--
 28 files changed, 816 insertions(+), 253 deletions(-)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt
 rename Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/{RequestGreenCertificateFragmentModule.kt => RequestCovidCertificateFragmentModule.kt} (71%)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt
 delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt
 rename Corona-Warn-App/src/main/res/layout/{fragment_request_green_certificate.xml => fragment_request_covid_certificate.xml} (93%)
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt

diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt
index 5c702e1e9..c7d115bc5 100644
--- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt
+++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/greencertificate/GreenCertificateTestFragment.kt
@@ -4,10 +4,7 @@ import android.annotation.SuppressLint
 import android.os.Bundle
 import android.view.View
 import androidx.fragment.app.Fragment
-import de.rki.coronawarnapp.NavGraphDirections
 import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR
-import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN
 import de.rki.coronawarnapp.databinding.FragmentTestGreenCertificateBinding
 import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
 import de.rki.coronawarnapp.util.di.AutoInject
@@ -28,13 +25,6 @@ class GreenCertificateTestFragment : Fragment(R.layout.fragment_test_green_certi
         super.onViewCreated(view, savedInstanceState)
 
         binding.apply {
-            pcrScreen.setOnClickListener {
-                doNavigate(NavGraphDirections.actionSubmissionTestResultGreenCertificateFragment(PCR))
-            }
-            ratScreen.setOnClickListener {
-                doNavigate(NavGraphDirections.actionSubmissionTestResultGreenCertificateFragment(RAPID_ANTIGEN))
-            }
-
             detailsScreen.setOnClickListener {
                 doNavigate(
                     GreenCertificateTestFragmentDirections
diff --git a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml
index 4f5a4eee7..b35ae07c4 100644
--- a/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml
+++ b/Corona-Warn-App/src/deviceForTesters/res/layout/fragment_test_green_certificate.xml
@@ -17,18 +17,6 @@
             android:layout_height="wrap_content"
             android:text="Request DCC screen" />
 
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/pcr_screen"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="CPR screen" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/rat_screen"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="RAT screen" />
-
         <com.google.android.material.button.MaterialButton
             android:id="@+id/details_screen"
             android:layout_width="match_parent"
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 766ca2e57..99ab06763 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
@@ -6,7 +6,7 @@ import org.joda.time.LocalDate
 interface TestRegistrationRequest {
     val type: CoronaTest.Type
     val identifier: String
-    val isDccSupportedbyPoc: Boolean
+    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 2e9417b64..e7660b6d5 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
@@ -21,7 +21,7 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest {
     ) : CoronaTestQRCode() {
 
         @IgnoredOnParcel
-        override val isDccSupportedbyPoc: Boolean = true
+        override val isDccSupportedByPoc: Boolean = true
 
         @IgnoredOnParcel
         override val type: CoronaTest.Type = CoronaTest.Type.PCR
@@ -37,15 +37,15 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest {
 
     @Parcelize
     data class RapidAntigen(
+        override val dateOfBirth: LocalDate? = null,
+        override val isDccConsentGiven: Boolean = false,
+        override val isDccSupportedByPoc: Boolean = false,
         val hash: RapidAntigenHash,
         val createdAt: Instant,
         val firstName: String? = null,
         val lastName: String? = null,
-        override val dateOfBirth: LocalDate? = null,
-        val testid: String? = null,
+        val testId: 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 f0bffc613..703886f8e 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
@@ -39,9 +39,9 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
             firstName = payload.firstName,
             lastName = payload.lastName,
             dateOfBirth = payload.dateOfBirth,
-            testid = payload.testId,
+            testId = payload.testId,
             salt = payload.salt,
-            isDccSupportedbyPoc = false, // TODO
+            isDccSupportedByPoc = payload.isDccSupportedByPoc
         )
     }
 
@@ -83,7 +83,8 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
         @SerializedName("ln") val lastName: String?,
         @SerializedName("dob") val dateOfBirth: String?,
         @SerializedName("testid") val testid: String?,
-        @SerializedName("salt") val salt: String?
+        @SerializedName("salt") val salt: String?,
+        @SerializedName("dgc") val dgc: Boolean?
     )
 
     private data class CleanPayload(val raw: RawPayload) {
@@ -127,6 +128,8 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
             if (raw.salt.isNullOrEmpty()) null else raw.salt
         }
 
+        val isDccSupportedByPoc: Boolean by lazy { raw.dgc == true }
+
         fun requireValidData() {
             requireValidPersonalData()
             requireValidHash()
@@ -144,9 +147,15 @@ class RapidAntigenQrCodeExtractor @Inject constructor() : QrCodeExtractor<Corona
 
         private fun requireValidHash() {
             val isQrCodeWithPersonalData = firstName != null && lastName != null && dateOfBirth != null
-            val generatedHash =
+            val rawBuilder = StringBuilder(
                 "${raw.dateOfBirth}#${raw.firstName}#${raw.lastName}#${raw.timestamp}#${raw.testid}#${raw.salt}"
-                    .toSHA256()
+            )
+            if (raw.dgc != null) {
+                val asInt = if (raw.dgc == true) 1 else 0
+                rawBuilder.append("#$asInt")
+            }
+
+            val generatedHash = rawBuilder.toString().toSHA256()
             if (isQrCodeWithPersonalData && !generatedHash.equals(hash, true)) {
                 throw InvalidQRCodeException("Generated hash doesn't match QRCode hash")
             }
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 348576e79..46b9e79ac 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
@@ -25,7 +25,7 @@ sealed class CoronaTestTAN : Parcelable, TestRegistrationRequest {
         override val type: CoronaTest.Type = CoronaTest.Type.PCR
 
         @IgnoredOnParcel
-        override val isDccSupportedbyPoc: Boolean = false
+        override val isDccSupportedByPoc: Boolean = false
 
         @IgnoredOnParcel
         override val isDccConsentGiven: Boolean = false
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 464bb89b5..f516a6e5e 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
@@ -28,6 +28,8 @@ interface CoronaTest {
      */
     val isFinal: Boolean
 
+    val isRedeemed: Boolean
+
     val testResultReceivedAt: Instant?
     val testResult: CoronaTestResult
 
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 fc85fb4eb..fd28cb447 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
@@ -54,6 +54,9 @@ data class PCRCoronaTest(
     override val isFinal: Boolean
         get() = testResult == CoronaTestResult.PCR_REDEEMED
 
+    override val isRedeemed: Boolean
+        get() = testResult == CoronaTestResult.PCR_REDEEMED
+
     override val isPositive: Boolean
         get() = testResult == CoronaTestResult.PCR_POSITIVE
 
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 13e0c4200..6dfa9ad11 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
@@ -99,6 +99,9 @@ data class RACoronaTest(
     override val isFinal: Boolean
         get() = testResult == RAT_REDEEMED
 
+    override val isRedeemed: Boolean
+        get() = testResult == RAT_REDEEMED
+
     override val isPositive: Boolean
         get() = testResult == RAT_POSITIVE
 
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 463d38167..c2b3911c6 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
@@ -101,7 +101,7 @@ class RAProcessor @Inject constructor(
             lastName = request.lastName,
             dateOfBirth = request.dateOfBirth,
             sampleCollectedAt = sampleCollectedAt,
-            isDccSupportedByPoc = request.isDccSupportedbyPoc,
+            isDccSupportedByPoc = request.isDccSupportedByPoc,
         )
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
index 4315b5004..5db57987c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/deletionwarning/SubmissionDeletionWarningViewModel.kt
@@ -49,36 +49,45 @@ class SubmissionDeletionWarningViewModel @AssistedInject constructor(
 
     private suspend fun deleteExistingAndRegisterNewTestWithQrCode() = try {
         requireNotNull(coronaTestQrCode) { "QR Code was unavailable" }
+        if (coronaTestQrCode.isDccSupportedByPoc) {
+            SubmissionDeletionWarningFragmentDirections
+                .actionSubmissionDeletionWarningFragmentToRequestCovidCertificateFragment(
+                    coronaTestQrCode = coronaTestQrCode,
+                    coronaTestConsent = isConsentGiven,
+                    deleteOldTest = true
+                ).run { routeToScreen.postValue(this) }
+        } else {
+            removeAndRegisterNew(coronaTestQrCode)
+        }
+    } catch (e: Exception) {
+        Timber.e(e, "Error during test registration via QR code")
+        mutableRegistrationState.postValue(RegistrationState(isFetching = false))
+        registrationError.postValue(e)
+    }
 
+    private suspend fun removeAndRegisterNew(
+        coronaTestQrCode: CoronaTestQRCode
+    ) {
         // Remove existing test and wait until that is done
         submissionRepository.testForType(coronaTestQrCode.type).first()?.let {
             coronaTestRepository.removeTest(it.identifier)
         } ?: Timber.w("Test we will replace with QR was already removed?")
 
         mutableRegistrationState.postValue(RegistrationState(isFetching = true))
-
         val coronaTest = submissionRepository.registerTest(coronaTestQrCode)
 
-        if (coronaTest.isFinal) {
-            Timber.d("New test was already final, removing it again: %s", coronaTest)
+        if (coronaTest.isRedeemed) {
+            Timber.d("New test was already redeemed, removing it again: %s", coronaTest)
             // This does not wait until the test is removed,
             // the exception handling should navigate the user to a new screen anyways
             submissionRepository.removeTestFromDevice(type = coronaTest.type)
-
-            throw InvalidQRCodeException()
+            throw InvalidQRCodeException("Test is already redeemed")
         }
 
-        if (isConsentGiven) {
-            submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type)
-        }
+        if (isConsentGiven) submissionRepository.giveConsentToSubmission(type = coronaTestQrCode.type)
 
         continueWithNewTest(coronaTest)
-
         mutableRegistrationState.postValue(RegistrationState(coronaTest = coronaTest))
-    } catch (e: Exception) {
-        Timber.e(e, "Error during test registration via QR code")
-        mutableRegistrationState.postValue(RegistrationState(isFetching = false))
-        registrationError.postValue(e)
     }
 
     private suspend fun deleteExistingAndRegisterNewTestWitTAN() = try {
@@ -108,28 +117,20 @@ class SubmissionDeletionWarningViewModel @AssistedInject constructor(
 
     private fun continueWithNewTest(coronaTest: CoronaTest) {
         Timber.d("Continuing with our new CoronaTest: %s", coronaTest)
+        val testType = coronaTestQrCode!!.type
         when (getRegistrationType()) {
-            RegistrationType.QR -> {
-                if (coronaTest.isPositive) {
-                    SubmissionDeletionWarningFragmentDirections
-                        .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(
-                            testType = coronaTestQrCode!!.type
-                        )
-                        .run { routeToScreen.postValue(this) }
-                } else {
-                    SubmissionDeletionWarningFragmentDirections
-                        .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(
-                            testType = coronaTestQrCode!!.type
-                        )
-                        .run { routeToScreen.postValue(this) }
-                }
+            RegistrationType.QR -> if (coronaTest.isPositive) {
+                SubmissionDeletionWarningFragmentDirections
+                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultAvailableFragment(testType)
+            } else {
+                SubmissionDeletionWarningFragmentDirections
+                    .actionSubmissionDeletionWarningFragmentToSubmissionTestResultPendingFragment(testType)
             }
-            RegistrationType.TAN -> {
+
+            RegistrationType.TAN ->
                 SubmissionDeletionWarningFragmentDirections
                     .actionSubmissionDeletionFragmentToSubmissionTestResultNoConsentFragment(getTestType())
-                    .run { routeToScreen.postValue(this) }
-            }
-        }
+        }.run { routeToScreen.postValue(this) }
     }
 
     data class RegistrationState(
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt
new file mode 100644
index 000000000..6ada9a974
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragment.kt
@@ -0,0 +1,189 @@
+package de.rki.coronawarnapp.ui.submission.greencertificate
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.View
+import androidx.core.view.isInvisible
+import androidx.core.view.isVisible
+import androidx.core.widget.doOnTextChanged
+import androidx.navigation.fragment.findNavController
+import androidx.navigation.fragment.navArgs
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import de.rki.coronawarnapp.NavGraphDirections
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.bugreporting.ui.toErrorDialogBuilder
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.server.CoronaTestResult
+import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.PCR
+import de.rki.coronawarnapp.coronatest.type.CoronaTest.Type.RAPID_ANTIGEN
+import de.rki.coronawarnapp.databinding.FragmentRequestCovidCertificateBinding
+import de.rki.coronawarnapp.exception.http.BadRequestException
+import de.rki.coronawarnapp.exception.http.CwaClientError
+import de.rki.coronawarnapp.exception.http.CwaServerError
+import de.rki.coronawarnapp.exception.http.CwaWebException
+import de.rki.coronawarnapp.ui.submission.ApiRequestState
+import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.util.DialogHelper
+import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
+import de.rki.coronawarnapp.util.di.AutoInject
+import de.rki.coronawarnapp.util.ui.doNavigate
+import de.rki.coronawarnapp.util.ui.popBackStack
+import de.rki.coronawarnapp.util.ui.viewBinding
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
+import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
+import org.joda.time.LocalDate
+import timber.log.Timber
+import javax.inject.Inject
+
+class RequestCovidCertificateFragment : Fragment(R.layout.fragment_request_covid_certificate), AutoInject {
+
+    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
+    private val viewModel by cwaViewModelsAssisted<RequestCovidCertificateViewModel>(
+        factoryProducer = { viewModelFactory },
+        constructorCall = { factory, _ ->
+            factory as RequestCovidCertificateViewModel.Factory
+            factory.create(args.coronaTestQrCode, args.coronaTestConsent, args.deleteOldTest)
+        }
+    )
+    private val binding by viewBinding<FragmentRequestCovidCertificateBinding>()
+    private val args by navArgs<RequestCovidCertificateFragmentArgs>()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
+        with(binding) {
+            val isPCR = args.coronaTestQrCode is CoronaTestQRCode.PCR
+            birthDateGroup.isVisible = isPCR
+            privacyCard.pcrExtraBullet.isVisible = isPCR
+
+            dateInputEdit.doOnTextChanged { text, _, _, _ ->
+                if (text.toString().isEmpty()) viewModel.birthDateChanged(null)
+            }
+
+            toolbar.setNavigationOnClickListener { showCloseDialog() }
+            agreeButton.setOnClickListener { viewModel.onAgreeGC() }
+            disagreeButton.setOnClickListener { viewModel.onDisagreeGC() }
+            dateInputEdit.setOnClickListener { openDatePicker() }
+            privacyInformation.setOnClickListener { findNavController().navigate(R.id.informationPrivacyFragment) }
+
+            viewModel.events.observe(viewLifecycleOwner) { event ->
+                when (event) {
+                    Back -> popBackStack()
+                    ToDispatcherScreen -> doNavigate(
+                        RequestCovidCertificateFragmentDirections
+                            .actionRequestCovidCertificateFragmentToDispatcherFragment()
+                    )
+                    ToHomeScreen -> doNavigate(
+                        RequestCovidCertificateFragmentDirections.actionRequestCovidCertificateFragmentToHomeFragment()
+                    )
+                }
+            }
+            viewModel.birthDate.observe(viewLifecycleOwner) { date -> agreeButton.isEnabled = !isPCR || date != null }
+            viewModel.registrationError.observe(viewLifecycleOwner) { DialogHelper.showDialog(buildErrorDialog(it)) }
+            viewModel.registrationState.observe(viewLifecycleOwner) { state -> handleRegistrationState(state) }
+            viewModel.showRedeemedTokenWarning.observe(viewLifecycleOwner) { DialogHelper.showDialog(redeemDialog()) }
+            viewModel.removalError.observe(viewLifecycleOwner) { it.toErrorDialogBuilder(requireContext()).show() }
+        }
+
+    private fun handleRegistrationState(state: QrCodeRegistrationStateProcessor.RegistrationState) {
+        when (state.apiRequestState) {
+            ApiRequestState.STARTED -> binding.apply {
+                progressBar.show()
+                agreeButton.isInvisible = true
+                disagreeButton.isInvisible = true
+            }
+            else -> binding.apply {
+                progressBar.hide()
+                agreeButton.isInvisible = false
+                disagreeButton.isInvisible = false
+            }
+        }
+
+        when (state.test?.testResult) {
+            CoronaTestResult.PCR_POSITIVE ->
+                NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = PCR)
+
+            CoronaTestResult.PCR_OR_RAT_PENDING ->
+                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = state.test.type)
+
+            CoronaTestResult.PCR_NEGATIVE,
+            CoronaTestResult.PCR_INVALID,
+            CoronaTestResult.PCR_REDEEMED ->
+                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = PCR)
+
+            CoronaTestResult.RAT_POSITIVE ->
+                NavGraphDirections.actionToSubmissionTestResultAvailableFragment(testType = RAPID_ANTIGEN)
+
+            CoronaTestResult.RAT_NEGATIVE,
+            CoronaTestResult.RAT_INVALID,
+            CoronaTestResult.RAT_PENDING,
+            CoronaTestResult.RAT_REDEEMED ->
+                NavGraphDirections.actionSubmissionTestResultPendingFragment(testType = RAPID_ANTIGEN)
+            null -> {
+                Timber.w("Successful API request, but test was null?")
+                return
+            }
+        }.run { doNavigate(this) }
+    }
+
+    private fun redeemDialog(): DialogHelper.DialogInstance = DialogHelper.DialogInstance(
+        requireActivity(),
+        R.string.submission_error_dialog_web_tan_redeemed_title,
+        R.string.submission_error_dialog_web_tan_redeemed_body,
+        R.string.submission_error_dialog_web_tan_redeemed_button_positive
+    )
+
+    private fun showCloseDialog() = MaterialAlertDialogBuilder(requireContext())
+        .setTitle(R.string.request_gc_dialog_title)
+        .setMessage(R.string.request_gc_dialog_message)
+        .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> viewModel.navigateBack() }
+        .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> viewModel.navigateToHomeScreen() }
+        .create()
+        .show()
+
+    private fun openDatePicker() = MaterialDatePicker.Builder
+        .datePicker()
+        .build()
+        .apply {
+            addOnPositiveButtonClickListener { timestamp ->
+                val localDate = LocalDate(timestamp)
+                binding.dateInputEdit.setText(localDate.toDayFormat())
+                viewModel.birthDateChanged(localDate)
+            }
+        }
+        .show(childFragmentManager, "RequestGreenCertificateFragment.MaterialDatePicker")
+
+    private fun buildErrorDialog(exception: CwaWebException): DialogHelper.DialogInstance =
+        when (exception) {
+            is BadRequestException -> createInvalidScanDialog()
+            is CwaClientError, is CwaServerError -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                R.string.submission_error_dialog_web_generic_network_error_body,
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                { viewModel.navigateToDispatcherScreen() }
+            )
+            else -> DialogHelper.DialogInstance(
+                requireActivity(),
+                R.string.submission_error_dialog_web_generic_error_title,
+                R.string.submission_error_dialog_web_generic_error_body,
+                R.string.submission_error_dialog_web_generic_error_button_positive,
+                null,
+                true,
+                { viewModel.navigateToDispatcherScreen() }
+            )
+        }
+
+    private fun createInvalidScanDialog() = DialogHelper.DialogInstance(
+        requireActivity(),
+        R.string.submission_qr_code_scan_invalid_dialog_headline,
+        R.string.submission_qr_code_scan_invalid_dialog_body,
+        R.string.submission_qr_code_scan_invalid_dialog_button_positive,
+        R.string.submission_qr_code_scan_invalid_dialog_button_negative,
+        true,
+        { viewModel.navigateBack() },
+        { viewModel.navigateToDispatcherScreen() },
+        { viewModel.navigateToDispatcherScreen() }
+    )
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt
similarity index 71%
rename from Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt
rename to Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt
index 216a51af4..74c2e8f20 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragmentModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateFragmentModule.kt
@@ -8,12 +8,12 @@ import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
 import de.rki.coronawarnapp.util.viewmodel.CWAViewModelKey
 
 @Module
-abstract class RequestGreenCertificateFragmentModule {
+abstract class RequestCovidCertificateFragmentModule {
 
     @Binds
     @IntoMap
-    @CWAViewModelKey(RequestGreenCertificateViewModel::class)
+    @CWAViewModelKey(RequestCovidCertificateViewModel::class)
     abstract fun requestGreenCertificateFragment(
-        factory: RequestGreenCertificateViewModel.Factory
+        factory: RequestCovidCertificateViewModel.Factory
     ): CWAViewModelFactory<out CWAViewModel>
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt
new file mode 100644
index 000000000..e6603079d
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModel.kt
@@ -0,0 +1,97 @@
+package de.rki.coronawarnapp.ui.submission.greencertificate
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
+import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
+import kotlinx.coroutines.flow.first
+import org.joda.time.LocalDate
+import timber.log.Timber
+
+class RequestCovidCertificateViewModel @AssistedInject constructor(
+    @Assisted private val coronaTestQrCode: CoronaTestQRCode,
+    @Assisted("coronaTestConsent") private val coronaTestConsent: Boolean,
+    @Assisted("deleteOldTest") private val deleteOldTest: Boolean,
+    private val qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor,
+    private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector,
+    private val submissionRepository: SubmissionRepository,
+    private val coronaTestRepository: CoronaTestRepository,
+) : CWAViewModel() {
+
+    // Test registration LiveData
+    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
+    val registrationState = qrCodeRegistrationStateProcessor.registrationState
+    val registrationError = qrCodeRegistrationStateProcessor.registrationError
+    val removalError = SingleLiveEvent<Throwable>()
+
+    private val birthDateData = MutableLiveData<LocalDate>(null)
+    val birthDate: LiveData<LocalDate> = birthDateData
+    val events = SingleLiveEvent<RequestDccNavEvent>()
+
+    fun birthDateChanged(localDate: LocalDate?) {
+        birthDateData.value = localDate
+    }
+
+    fun onAgreeGC() = registerAndMaybeDelete(dccConsent = true)
+
+    fun onDisagreeGC() = registerAndMaybeDelete(dccConsent = false)
+
+    fun navigateBack() {
+        events.postValue(Back)
+    }
+
+    fun navigateToHomeScreen() {
+        events.postValue(ToHomeScreen)
+    }
+
+    fun navigateToDispatcherScreen() {
+        events.postValue(ToDispatcherScreen)
+    }
+
+    private fun registerAndMaybeDelete(dccConsent: Boolean) = launch {
+        if (deleteOldTest) removeOldTest()
+        registerWithDccConsent(dccConsent)
+    }
+
+    private suspend fun registerWithDccConsent(dccConsent: Boolean) {
+        val consentedQrCode = when (coronaTestQrCode) {
+            is CoronaTestQRCode.PCR -> coronaTestQrCode.copy(
+                dateOfBirth = birthDateData.value,
+                isDccConsentGiven = dccConsent
+            )
+            is CoronaTestQRCode.RapidAntigen -> coronaTestQrCode.copy(isDccConsentGiven = dccConsent)
+        }
+
+        qrCodeRegistrationStateProcessor.startQrCodeRegistration(consentedQrCode, coronaTestConsent)
+        if (coronaTestConsent) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(consentedQrCode.type)
+    }
+
+    private suspend fun removeOldTest() {
+        try {
+            submissionRepository.testForType(coronaTestQrCode.type).first()?.let {
+                coronaTestRepository.removeTest(it.identifier)
+            } ?: Timber.e("Test for type ${coronaTestQrCode.type} is not found")
+        } catch (e: Exception) {
+            Timber.d(e, "removeOldTest failed")
+            removalError.postValue(e)
+        }
+    }
+
+    @AssistedFactory
+    interface Factory : CWAViewModelFactory<RequestCovidCertificateViewModel> {
+        fun create(
+            coronaTestQrCode: CoronaTestQRCode,
+            @Assisted("coronaTestConsent") coronaTestConsent: Boolean,
+            @Assisted("deleteOldTest") deleteOldTest: Boolean
+        ): RequestCovidCertificateViewModel
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt
new file mode 100644
index 000000000..1bb251b76
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestDccNavEvent.kt
@@ -0,0 +1,6 @@
+package de.rki.coronawarnapp.ui.submission.greencertificate
+
+sealed class RequestDccNavEvent
+object ToDispatcherScreen : RequestDccNavEvent()
+object ToHomeScreen : RequestDccNavEvent()
+object Back : RequestDccNavEvent()
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt
deleted file mode 100644
index 22c6a1a82..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateFragment.kt
+++ /dev/null
@@ -1,74 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.greencertificate
-
-import android.os.Bundle
-import androidx.fragment.app.Fragment
-import android.view.View
-import androidx.core.view.isVisible
-import androidx.core.widget.doOnTextChanged
-import androidx.navigation.fragment.navArgs
-import com.google.android.material.datepicker.MaterialDatePicker
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
-import de.rki.coronawarnapp.R
-import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.databinding.FragmentRequestGreenCertificateBinding
-import de.rki.coronawarnapp.util.TimeAndDateExtensions.toDayFormat
-import de.rki.coronawarnapp.util.di.AutoInject
-import de.rki.coronawarnapp.util.ui.viewBinding
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
-import de.rki.coronawarnapp.util.viewmodel.cwaViewModelsAssisted
-import org.joda.time.LocalDate
-import javax.inject.Inject
-
-class RequestGreenCertificateFragment : Fragment(R.layout.fragment_request_green_certificate), AutoInject {
-
-    @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory
-    private val viewModel by cwaViewModelsAssisted<RequestGreenCertificateViewModel>(
-        factoryProducer = { viewModelFactory },
-        constructorCall = { factory, _ ->
-            factory as RequestGreenCertificateViewModel.Factory
-            factory.create(args.testType)
-        }
-    )
-    private val binding by viewBinding<FragmentRequestGreenCertificateBinding>()
-    private val args by navArgs<RequestGreenCertificateFragmentArgs>()
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) =
-        with(binding) {
-            val isPCR = args.testType == CoronaTest.Type.PCR
-            birthDateGroup.isVisible = isPCR
-            privacyCard.pcrExtraBullet.isVisible = isPCR
-
-            dateInputEdit.doOnTextChanged { text, _, _, _ ->
-                if (text.toString().isEmpty()) viewModel.birthDateChanged(null)
-            }
-
-            toolbar.setNavigationOnClickListener { showDialog() }
-            agreeButton.setOnClickListener { viewModel.onAgreeGC() }
-            disagreeButton.setOnClickListener { viewModel.onDisagreeGC() }
-            dateInputEdit.setOnClickListener { openDatePicker() }
-        }
-
-    private fun showDialog() {
-        MaterialAlertDialogBuilder(requireContext())
-            .setTitle(R.string.request_gc_dialog_title)
-            .setMessage(R.string.request_gc_dialog_message)
-            .setNegativeButton(R.string.request_gc_dialog_negative_button) { _, _ -> /* TODO */ }
-            .setPositiveButton(R.string.request_gc_dialog_positive_button) { _, _ -> /* TODO */ }
-            .create()
-            .show()
-    }
-
-    private fun openDatePicker() {
-        MaterialDatePicker.Builder
-            .datePicker()
-            .build()
-            .apply {
-                addOnPositiveButtonClickListener { timestamp ->
-                    val localDate = LocalDate(timestamp)
-                    binding.dateInputEdit.setText(localDate.toDayFormat())
-                    viewModel.birthDateChanged(localDate)
-                }
-            }
-            .show(childFragmentManager, "RequestGreenCertificateFragment.MaterialDatePicker")
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt
deleted file mode 100644
index dcefb79db..000000000
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestGreenCertificateViewModel.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-package de.rki.coronawarnapp.ui.submission.greencertificate
-
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
-import de.rki.coronawarnapp.coronatest.type.CoronaTest
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
-import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactory
-import org.joda.time.LocalDate
-
-class RequestGreenCertificateViewModel @AssistedInject constructor(
-    @Assisted private val testType: CoronaTest.Type,
-) : CWAViewModel() {
-
-    fun birthDateChanged(localDate: LocalDate?) {
-        // TODO
-    }
-
-    fun onAgreeGC() {
-        // TODO
-    }
-
-    fun onDisagreeGC() {
-        // TODO
-    }
-
-    @AssistedFactory
-    interface Factory : CWAViewModelFactory<RequestGreenCertificateViewModel> {
-        fun create(type: CoronaTest.Type): RequestGreenCertificateViewModel
-    }
-}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt
index e0e8fd033..19ec160f5 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/QrCodeRegistrationStateProcessor.kt
@@ -34,12 +34,7 @@ class QrCodeRegistrationStateProcessor @Inject constructor(
                 submissionRepository.giveConsentToSubmission(type = coronaTestQRCode.type)
             }
             checkTestResult(coronaTestQRCode, coronaTest)
-            registrationState.postValue(
-                RegistrationState(
-                    ApiRequestState.SUCCESS,
-                    coronaTest
-                )
-            )
+            registrationState.postValue(RegistrationState(ApiRequestState.SUCCESS, coronaTest))
         } catch (err: CwaWebException) {
             registrationState.postValue(RegistrationState(ApiRequestState.FAILED))
             registrationError.postValue(err)
@@ -54,7 +49,7 @@ class QrCodeRegistrationStateProcessor @Inject constructor(
         }
 
     private fun checkTestResult(request: CoronaTestQRCode, test: CoronaTest) {
-        if (test.isFinal) {
+        if (test.isRedeemed) {
             throw InvalidQRCodeException("CoronaTestResult already redeemed ${request.registrationIdentifier}")
         }
     }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
index cc3e41d58..6839584b0 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanFragment.kt
@@ -68,7 +68,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
             submissionQrCodeScanViewfinderView.setCameraPreview(submissionQrCodeScanPreview)
         }
 
-        viewModel.routeToScreen.observe2(this) {
+        viewModel.events.observe2(this) {
             when (it) {
                 is SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode -> {
                     NavGraphDirections
@@ -77,6 +77,9 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
                 }
                 is SubmissionNavigationEvents.NavigateToDispatcher -> navigateToDispatchScreen()
                 is SubmissionNavigationEvents.NavigateToConsent -> goBack()
+                is SubmissionNavigationEvents.NavigateToRequestDccFragment -> doNavigate(
+                    NavGraphDirections.actionRequestCovidCertificateFragment(it.coronaTestQRCode, it.consentGiven)
+                )
             }
         }
 
@@ -136,7 +139,7 @@ class SubmissionQRCodeScanFragment : Fragment(R.layout.fragment_submission_qr_co
 
     private fun startDecode() {
         binding.submissionQrCodeScanPreview.decodeSingle {
-            viewModel.onQrCodeAvailable(it.text)
+            viewModel.registerCoronaTest(it.text)
         }
     }
 
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 cc2f2087c..b1e0a8348 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
@@ -26,49 +26,43 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor(
     private val qrCodeValidator: CoronaTestQrCodeValidator,
     private val analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 ) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
-    val routeToScreen = SingleLiveEvent<SubmissionNavigationEvents>()
-    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
+    val events = SingleLiveEvent<SubmissionNavigationEvents>()
     val qrCodeValidationState = SingleLiveEvent<QrCodeRegistrationStateProcessor.ValidationState>()
+    val showRedeemedTokenWarning = qrCodeRegistrationStateProcessor.showRedeemedTokenWarning
     val registrationState = qrCodeRegistrationStateProcessor.registrationState
     val registrationError = qrCodeRegistrationStateProcessor.registrationError
 
-    fun onQrCodeAvailable(rawResult: String) {
-        launch {
-            startQrCodeRegistration(rawResult, isConsentGiven)
-        }
-    }
-
-    suspend fun startQrCodeRegistration(rawResult: String, isConsentGiven: Boolean) {
+    fun registerCoronaTest(rawResult: String) = launch {
         try {
-            val coronaTestQRCode = qrCodeValidator.validate(rawResult)
+            val ctQrCode = qrCodeValidator.validate(rawResult)
             qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.SUCCESS)
-            val coronaTest = submissionRepository.testForType(coronaTestQRCode.type).first()
-
-            if (coronaTest != null) {
-                routeToScreen.postValue(
+            val coronaTest = submissionRepository.testForType(ctQrCode.type).first()
+            when {
+                coronaTest != null -> events.postValue(
                     SubmissionNavigationEvents.NavigateToDeletionWarningFragmentFromQrCode(
-                        coronaTestQRCode = coronaTestQRCode,
+                        coronaTestQRCode = ctQrCode,
                         consentGiven = isConsentGiven
                     )
                 )
-            } else {
-                qrCodeRegistrationStateProcessor.startQrCodeRegistration(coronaTestQRCode, isConsentGiven)
-                if (isConsentGiven) {
-                    analyticsKeySubmissionCollector.reportAdvancedConsentGiven(coronaTestQRCode.type)
+
+                else -> if (!ctQrCode.isDccSupportedByPoc) {
+                    qrCodeRegistrationStateProcessor.startQrCodeRegistration(ctQrCode, isConsentGiven)
+                    if (isConsentGiven) analyticsKeySubmissionCollector.reportAdvancedConsentGiven(ctQrCode.type)
+                } else {
+                    events.postValue(
+                        SubmissionNavigationEvents.NavigateToRequestDccFragment(ctQrCode, isConsentGiven)
+                    )
                 }
             }
         } catch (err: InvalidQRCodeException) {
+            Timber.d(err, "Invalid QrCode")
             qrCodeValidationState.postValue(QrCodeRegistrationStateProcessor.ValidationState.INVALID)
         }
     }
 
-    fun onBackPressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToConsent)
-    }
+    fun onBackPressed() = events.postValue(SubmissionNavigationEvents.NavigateToConsent)
 
-    fun onClosePressed() {
-        routeToScreen.postValue(SubmissionNavigationEvents.NavigateToDispatcher)
-    }
+    fun onClosePressed() = events.postValue(SubmissionNavigationEvents.NavigateToDispatcher)
 
     fun setCameraDeniedPermanently(denied: Boolean) {
         Timber.d("setCameraDeniedPermanently(denied=$denied)")
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
index 64d13707e..2f0c1d509 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionFragmentModule.kt
@@ -8,8 +8,8 @@ import de.rki.coronawarnapp.ui.submission.fragment.SubmissionContactFragment
 import de.rki.coronawarnapp.ui.submission.fragment.SubmissionDispatcherFragment
 import de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningFragment
 import de.rki.coronawarnapp.ui.submission.deletionwarning.SubmissionDeletionWarningModule
-import de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragment
-import de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragmentModule
+import de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragment
+import de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragmentModule
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentFragment
 import de.rki.coronawarnapp.ui.submission.qrcode.consent.SubmissionConsentModule
 import de.rki.coronawarnapp.ui.submission.qrcode.scan.SubmissionQRCodeScanFragment
@@ -113,6 +113,6 @@ internal abstract class SubmissionFragmentModule {
     @ContributesAndroidInjector(modules = [SubmissionTestResultKeysSharedModule::class])
     abstract fun submissionTestResultKeysSharedScreen(): SubmissionTestResultKeysSharedFragment
 
-    @ContributesAndroidInjector(modules = [RequestGreenCertificateFragmentModule::class])
-    abstract fun requestGreenCertificateFragment(): RequestGreenCertificateFragment
+    @ContributesAndroidInjector(modules = [RequestCovidCertificateFragmentModule::class])
+    abstract fun requestGreenCertificateFragment(): RequestCovidCertificateFragment
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
index 789a64d88..dcd2b83ca 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/viewmodel/SubmissionNavigationEvents.kt
@@ -16,12 +16,16 @@ sealed class SubmissionNavigationEvents {
     object NavigateToConsent : SubmissionNavigationEvents()
     object NavigateToMainActivity : SubmissionNavigationEvents()
     data class NavigateToResultPendingScreen(var coronaTestType: CoronaTest.Type) : SubmissionNavigationEvents()
-    data class NavigateToResultAvailableScreen(var coronaTestType: CoronaTest.Type) : SubmissionNavigationEvents()
     data class NavigateToDeletionWarningFragmentFromQrCode(
         val coronaTestQRCode: CoronaTestQRCode,
         val consentGiven: Boolean
     ) : SubmissionNavigationEvents()
 
+    data class NavigateToRequestDccFragment(
+        val coronaTestQRCode: CoronaTestQRCode,
+        val consentGiven: Boolean
+    ) : SubmissionNavigationEvents()
+
     data class NavigateToDeletionWarningFragmentFromTan(val coronaTestTan: CoronaTestTAN, val consentGiven: Boolean) :
         SubmissionNavigationEvents()
 
diff --git a/Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml b/Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml
similarity index 93%
rename from Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml
rename to Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml
index 7c6c51477..7129eb51e 100644
--- a/Corona-Warn-App/src/main/res/layout/fragment_request_green_certificate.xml
+++ b/Corona-Warn-App/src/main/res/layout/fragment_request_covid_certificate.xml
@@ -5,7 +5,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/colorBackground"
-    tools:context="ui.submission.greencertificate.RequestGreenCertificateFragment">
+    tools:context="ui.submission.greencertificate.RequestCovidCertificateFragment">
 
     <com.google.android.material.appbar.MaterialToolbar
         android:id="@+id/toolbar"
@@ -204,4 +204,17 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent" />
 
+    <com.google.android.material.progressindicator.CircularProgressIndicator
+        android:id="@+id/progress_bar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:indeterminate="true"
+        app:indicatorColor="@color/colorAccent"
+        app:layout_constraintBottom_toBottomOf="@+id/disagree_button"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/scrollview"
+        app:showAnimationBehavior="inward"
+        app:trackColor="@android:color/transparent" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
index a5cb4fd1e..919fadb96 100644
--- a/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
+++ b/Corona-Warn-App/src/main/res/navigation/nav_graph.xml
@@ -395,8 +395,8 @@
         app:popUpToInclusive="false" />
 
     <action
-        android:id="@+id/action_submissionTestResultGreenCertificateFragment"
-        app:destination="@id/requestGreenCertificateFragment" />
+        android:id="@+id/action_requestCovidCertificateFragment"
+        app:destination="@id/requestCovidCertificateFragment" />
 
     <action
         android:id="@+id/action_to_submissionTestResultAvailableFragment"
@@ -753,6 +753,12 @@
             app:destination="@id/submissionConsentFragment"
             app:popUpTo="@id/mainFragment"
             app:popUpToInclusive="false" />
+
+        <action
+            android:id="@+id/action_submissionDeletionWarningFragment_to_requestCovidCertificateFragment"
+            app:destination="@id/requestCovidCertificateFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
     </fragment>
 
     <fragment
@@ -823,12 +829,31 @@
         android:id="@+id/action_global_qrCodeFullScreenFragment"
         app:destination="@id/qrCodeFullScreenFragment" />
     <fragment
-        android:id="@+id/requestGreenCertificateFragment"
-        android:name="de.rki.coronawarnapp.ui.submission.greencertificate.RequestGreenCertificateFragment"
-        android:label="fragment_request_green_certificate"
-        tools:layout="@layout/fragment_request_green_certificate">
+        android:id="@+id/requestCovidCertificateFragment"
+        android:name="de.rki.coronawarnapp.ui.submission.greencertificate.RequestCovidCertificateFragment"
+        android:label="fragment_request_covid_certificate"
+        tools:layout="@layout/fragment_request_covid_certificate">
         <argument
-            android:name="testType"
-            app:argType="de.rki.coronawarnapp.coronatest.type.CoronaTest$Type" />
+            android:name="coronaTestQrCode"
+            app:argType="de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode" />
+        <argument
+            android:name="coronaTestConsent"
+            android:defaultValue="false"
+            app:argType="boolean" />
+
+        <argument
+            android:name="deleteOldTest"
+            android:defaultValue="false"
+            app:argType="boolean" />
+
+        <action
+            android:id="@+id/action_requestCovidCertificateFragment_to_dispatcherFragment"
+            app:popUpTo="@id/submissionDispatcherFragment"
+            app:popUpToInclusive="false" />
+
+        <action
+            android:id="@+id/action_requestCovidCertificateFragment_to_homeFragment"
+            app:popUpTo="@id/mainFragment"
+            app:popUpToInclusive="false" />
     </fragment>
 </navigation>
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
index a1d45959d..8ef96725b 100644
--- 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
@@ -8,12 +8,12 @@ import testhelpers.BaseTest
 class CoronaTestQRCodeTest : BaseTest() {
 
     private val instancePCR = CoronaTestQRCode.PCR("pcr")
-    private val instanceRA = CoronaTestQRCode.RapidAntigen("ra", createdAt = Instant.EPOCH)
+    private val instanceRA = CoronaTestQRCode.RapidAntigen(hash = "ra", createdAt = Instant.EPOCH)
 
     @Test
     fun `PCR defaults`() {
         instancePCR.apply {
-            isDccSupportedbyPoc shouldBe true
+            isDccSupportedByPoc shouldBe true
             isDccConsentGiven shouldBe false
             dateOfBirth shouldBe null
         }
@@ -22,7 +22,7 @@ class CoronaTestQRCodeTest : BaseTest() {
     @Test
     fun `RA defaults`() {
         instanceRA.apply {
-            isDccSupportedbyPoc shouldBe false
+            isDccSupportedByPoc shouldBe false
             isDccConsentGiven shouldBe false
             dateOfBirth shouldBe null
         }
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
index 20b1559d6..7de9dca1b 100644
--- 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
@@ -12,7 +12,7 @@ class CoronaTestTANTest : BaseTest() {
     fun `dcc is not supported by tans`() {
         instancePCR.apply {
             isDccConsentGiven shouldBe false
-            isDccSupportedbyPoc shouldBe false
+            isDccSupportedByPoc shouldBe false
             dateOfBirth shouldBe null
         }
     }
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt
new file mode 100644
index 000000000..bd9be00a5
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/greencertificate/RequestCovidCertificateViewModelTest.kt
@@ -0,0 +1,304 @@
+package de.rki.coronawarnapp.ui.submission.greencertificate
+
+import androidx.lifecycle.MutableLiveData
+import de.rki.coronawarnapp.coronatest.CoronaTestRepository
+import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode
+import de.rki.coronawarnapp.coronatest.type.CoronaTest
+import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector
+import de.rki.coronawarnapp.submission.SubmissionRepository
+import de.rki.coronawarnapp.ui.submission.qrcode.QrCodeRegistrationStateProcessor
+import de.rki.coronawarnapp.util.ui.SingleLiveEvent
+import io.kotest.matchers.shouldBe
+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.flow.flowOf
+import org.joda.time.Instant
+import org.joda.time.LocalDate
+import org.joda.time.format.DateTimeFormat
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.extension.ExtendWith
+import testhelpers.BaseTest
+import testhelpers.extensions.InstantExecutorExtension
+import testhelpers.extensions.getOrAwaitValue
+
+@ExtendWith(InstantExecutorExtension::class)
+internal class RequestCovidCertificateViewModelTest : BaseTest() {
+
+    @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
+    @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
+    @MockK lateinit var submissionRepository: SubmissionRepository
+    @MockK lateinit var coronaTestRepository: CoronaTestRepository
+    @MockK lateinit var coronaTest: CoronaTest
+
+    private val date = LocalDate.parse(
+        "01.01.1987",
+        DateTimeFormat.forPattern("dd.MM.yyyy")
+    )
+
+    private val ratQRCode = CoronaTestQRCode.RapidAntigen(hash = "hash", dateOfBirth = date, createdAt = Instant.EPOCH)
+    private val pcrQRCode = CoronaTestQRCode.PCR("GUID")
+
+    @BeforeEach
+    fun setup() {
+        MockKAnnotations.init(this)
+
+        qrCodeRegistrationStateProcessor.apply {
+            coEvery { startQrCodeRegistration(any(), any()) } just Runs
+            coEvery { registrationError } returns SingleLiveEvent()
+            coEvery { showRedeemedTokenWarning } returns SingleLiveEvent()
+            coEvery { registrationState } returns MutableLiveData()
+        }
+
+        submissionRepository.apply {
+            coEvery { registerTest(any()) } returns coronaTest
+            coEvery { testForType(any()) } returns flowOf(coronaTest)
+        }
+
+        coEvery { coronaTestRepository.removeTest(any()) } returns coronaTest
+
+        every { coronaTest.identifier } returns "identifier"
+        every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+    }
+
+    @Test
+    fun birthDateChanged() {
+        createInstance().apply {
+            birthDateChanged(date)
+            birthDate.getOrAwaitValue() shouldBe date
+        }
+    }
+
+    @Test
+    fun `PCR onAgreeGC removes and registers new test`() {
+        createInstance(deleteOldTest = true).apply {
+            birthDateChanged(date)
+            onAgreeGC()
+
+            coVerify {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `PCR onAgreeGC registers new test and does not remove old Test`() {
+        createInstance(deleteOldTest = false).apply {
+            birthDateChanged(date)
+            onAgreeGC()
+
+            coVerify {
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    pcrQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+
+            coVerify(exactly = 0) {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+            }
+        }
+    }
+
+    @Test
+    fun `PCR onDisagreeGC removes and registers new test`() {
+        createInstance(deleteOldTest = true).apply {
+            onDisagreeGC()
+
+            coVerify {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    pcrQRCode.copy(isDccConsentGiven = false),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `PCR onDisagreeGC registers new test and does not remove old Test`() {
+        createInstance(deleteOldTest = false).apply {
+            onDisagreeGC()
+
+            coVerify {
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    pcrQRCode.copy(isDccConsentGiven = false),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+
+            coVerify(exactly = 0) {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+            }
+        }
+    }
+
+    @Test
+    fun `RAT onAgreeGC removes and registers new test`() {
+        createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = true).apply {
+            onAgreeGC()
+
+            coVerify {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    ratQRCode.copy(isDccConsentGiven = true, dateOfBirth = date),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `RAT onAgreeGC registers new test and does not remove old Test`() {
+        createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = false).apply {
+            onAgreeGC()
+
+            coVerify {
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    ratQRCode.copy(isDccConsentGiven = true),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+
+            coVerify(exactly = 0) {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+            }
+        }
+    }
+
+    @Test
+    fun `RAT onDisagreeGC removes and registers new test`() {
+        createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = true).apply {
+            onDisagreeGC()
+
+            coVerify {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    ratQRCode.copy(isDccConsentGiven = false),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `RAT onDisagreeGC registers new test and does not remove old Test`() {
+        createInstance(coronaTestQRCode = ratQRCode, deleteOldTest = false).apply {
+            onDisagreeGC()
+
+            coVerify {
+                qrCodeRegistrationStateProcessor.startQrCodeRegistration(
+                    ratQRCode.copy(isDccConsentGiven = false),
+                    any()
+                )
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+
+            coVerify(exactly = 0) {
+                submissionRepository.testForType(any())
+                coronaTestRepository.removeTest(any())
+            }
+        }
+    }
+
+    @Test
+    fun navigateBack() {
+        createInstance().apply {
+            navigateBack()
+            events.getOrAwaitValue() shouldBe Back
+        }
+    }
+
+    @Test
+    fun navigateToHomeScreen() {
+        createInstance().apply {
+            navigateToHomeScreen()
+            events.getOrAwaitValue() shouldBe ToHomeScreen
+        }
+    }
+
+    @Test
+    fun navigateToDispatcherScreen() {
+        createInstance().apply {
+            navigateToDispatcherScreen()
+            events.getOrAwaitValue() shouldBe ToDispatcherScreen
+        }
+    }
+
+    @Test
+    fun `onAgreeGC reports analytics`() {
+        createInstance(coronTestConsent = true).apply {
+            onAgreeGC()
+            coVerify {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `onDisagreeGC reports analytics`() {
+        createInstance(coronTestConsent = true).apply {
+            onDisagreeGC()
+            coVerify {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `onAgreeGC does not report analytics`() {
+        createInstance(coronTestConsent = false).apply {
+            onAgreeGC()
+            coVerify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    @Test
+    fun `onDisagreeGC does not report analytics`() {
+        createInstance(coronTestConsent = false).apply {
+            onDisagreeGC()
+            coVerify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any())
+            }
+        }
+    }
+
+    private fun createInstance(
+        coronaTestQRCode: CoronaTestQRCode = pcrQRCode,
+        coronTestConsent: Boolean = true,
+        deleteOldTest: Boolean = false
+    ) = RequestCovidCertificateViewModel(
+        coronaTestQrCode = coronaTestQRCode,
+        coronaTestConsent = coronTestConsent,
+        deleteOldTest = deleteOldTest,
+        coronaTestRepository = coronaTestRepository,
+        submissionRepository = submissionRepository,
+        qrCodeRegistrationStateProcessor = qrCodeRegistrationStateProcessor,
+        analyticsKeySubmissionCollector = analyticsKeySubmissionCollector
+    )
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
index 92d0fba01..18b2c8425 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt
@@ -22,6 +22,7 @@ import io.mockk.just
 import io.mockk.verify
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runBlockingTest
+import org.joda.time.Instant
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.extension.ExtendWith
@@ -39,15 +40,11 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
     @MockK lateinit var qrCodeRegistrationStateProcessor: QrCodeRegistrationStateProcessor
     @MockK lateinit var analyticsKeySubmissionCollector: AnalyticsKeySubmissionCollector
 
-    private val coronaTestFlow = MutableStateFlow<CoronaTest?>(
-        null
-    )
-
     @BeforeEach
     fun setUp() {
         MockKAnnotations.init(this)
 
-        every { submissionRepository.testForType(any()) } returns coronaTestFlow
+        every { submissionRepository.testForType(any()) } returns MutableStateFlow<CoronaTest?>(null)
         coEvery { qrCodeRegistrationStateProcessor.showRedeemedTokenWarning } returns SingleLiveEvent()
         coEvery { qrCodeRegistrationStateProcessor.registrationState } returns MutableLiveData(
             QrCodeRegistrationStateProcessor.RegistrationState(ApiRequestState.IDLE)
@@ -86,12 +83,12 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
 
         viewModel.qrCodeValidationState.value shouldBe ValidationState.STARTED
 
-        viewModel.onQrCodeAvailable(validQrCode)
+        viewModel.registerCoronaTest(validQrCode)
         viewModel.qrCodeValidationState.observeForever {}
         viewModel.qrCodeValidationState.value shouldBe ValidationState.SUCCESS
 
         // invalid guid
-        viewModel.onQrCodeAvailable(invalidQrCode)
+        viewModel.registerCoronaTest(invalidQrCode)
         viewModel.qrCodeValidationState.value shouldBe ValidationState.INVALID
     }
 
@@ -104,34 +101,79 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() {
     }
 
     @Test
-    fun `startQrCodeRegistration() should call analyticsKeySubmissionCollector for PCR tests`() = runBlockingTest {
-        val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
+    fun `registerCoronaTest() should call analyticsKeySubmissionCollector for PCR tests`() =
+        runBlockingTest {
+            val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
 
-        every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-        every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-        coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
+            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
+            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
 
-        createViewModel().startQrCodeRegistration(rawResult = "", isConsentGiven = true)
+            createViewModel().registerCoronaTest(rawResult = "")
 
-        verify(exactly = 1) { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) }
-        verify(exactly = 0) {
-            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+            verify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+            }
         }
-    }
 
     @Test
-    fun `startQrCodeRegistration() should NOT call analyticsKeySubmissionCollector for RAT tests`() = runBlockingTest {
-        val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
+    fun `registerCoronaTest() should NOT call analyticsKeySubmissionCollector for RAT tests`() =
+        runBlockingTest {
+            val coronaTestQRCode = CoronaTestQRCode.PCR(qrCodeGUID = "123456-12345678-1234-4DA7-B166-B86D85475064")
 
-        every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
-        every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
-        coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
+            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
+            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
 
-        createViewModel().startQrCodeRegistration(rawResult = "", isConsentGiven = true)
+            createViewModel().registerCoronaTest(rawResult = "")
 
-        verify(exactly = 1) { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR) }
-        verify(exactly = 0) {
-            analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+            verify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+            }
+        }
+
+    @Test
+    fun `registerCoronaTest() should call analyticsKeySubmissionCollector for RAT tests - no-dcc support`() =
+        runBlockingTest {
+            val coronaTestQRCode = CoronaTestQRCode.RapidAntigen(
+                hash = "123456-12345678-1234-4DA7-B166-B86D85475064",
+                createdAt = Instant.EPOCH,
+                isDccSupportedByPoc = false
+            )
+
+            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
+            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
+
+            createViewModel().registerCoronaTest(rawResult = "")
+
+            verify(exactly = 1) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+            }
+            verify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+            }
+        }
+
+    @Test
+    fun `registerCoronaTest() should Not call analyticsKeySubmissionCollector for RAT tests - dcc support`() =
+        runBlockingTest {
+            val coronaTestQRCode = CoronaTestQRCode.RapidAntigen(
+                hash = "123456-12345678-1234-4DA7-B166-B86D85475064",
+                createdAt = Instant.EPOCH,
+                isDccSupportedByPoc = true
+            )
+
+            every { qrCodeValidator.validate(any()) } returns coronaTestQRCode
+            every { analyticsKeySubmissionCollector.reportAdvancedConsentGiven(any()) } just Runs
+            coEvery { qrCodeRegistrationStateProcessor.startQrCodeRegistration(any(), any()) } just Runs
+
+            createViewModel().registerCoronaTest(rawResult = "")
+            verify(exactly = 0) {
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.RAPID_ANTIGEN)
+                analyticsKeySubmissionCollector.reportAdvancedConsentGiven(CoronaTest.Type.PCR)
+            }
         }
-    }
 }
-- 
GitLab