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