From 683c290dedf9dbf0c8a19f37f5f10fef5d4c055f Mon Sep 17 00:00:00 2001
From: Chilja Gossow <49635654+chiljamgossow@users.noreply.github.com>
Date: Fri, 20 Nov 2020 12:37:36 +0100
Subject: [PATCH] Persist consent to key submission (EXPOSUREAPP-3734) (#1633)

* create SubmissionSettings and add to SubmissionRepository

* refactor DI

* refactor testcode

* refactoring and test code

* klint

* remove AssistedInject

* split consent method

Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com>
---
 .../storage/KeyCacheDatabaseTest.kt           |   6 +-
 .../ui/submission/SubmissionSettingsTest.kt   |  24 +++
 ...isTestResultRetrievalPeriodicWorkerTest.kt |   9 +-
 .../de/rki/coronawarnapp/playbook/Playbook.kt |   8 +-
 .../service/submission/SubmissionService.kt   |  70 ++-----
 .../storage/RiskLevelRepository.kt            |   2 -
 .../storage/SubmissionRepository.kt           | 175 ++++++++++++------
 .../submission/SubmissionSettings.kt          |  22 +++
 .../submission/SubmissionTask.kt              |   4 +-
 .../ui/main/home/HomeFragmentViewModel.kt     |  12 +-
 .../main/home/SubmissionCardsStateProvider.kt |   8 +-
 .../ui/settings/SettingsResetViewModel.kt     |   4 +-
 .../fragment/SubmissionDispatcherFragment.kt  |   6 +-
 .../scan/SubmissionQRCodeScanViewModel.kt     |  12 +-
 .../submission/tan/SubmissionTanViewModel.kt  |   8 +-
 .../SubmissionTestResultFragment.kt           |   5 +-
 .../SubmissionTestResultViewModel.kt          |  22 ++-
 ...sionResultPositiveOtherWarningViewModel.kt |   4 +-
 .../ui/viewmodel/SubmissionViewModel.kt       |  11 +-
 .../de/rki/coronawarnapp/util/DataReset.kt    |   5 +-
 ...gnosisTestResultRetrievalPeriodicWorker.kt |   7 +-
 .../main/home/HomeFragmentViewModelTest.kt    |   5 +-
 .../home/SubmissionCardsStateProviderTest.kt  |  12 +-
 .../submission/SubmissionServiceTest.kt       |  94 ++--------
 .../storage/SubmissionRepositoryTest.kt       | 115 ++++++++++++
 .../scan/SubmissionQRCodeScanViewModelTest.kt |   4 +-
 .../tan/SubmissionTanViewModelTest.kt         |  13 +-
 27 files changed, 409 insertions(+), 258 deletions(-)
 create mode 100644 Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/ui/submission/SubmissionSettingsTest.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/submission/SubmissionSettings.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/storage/SubmissionRepositoryTest.kt

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