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 651c5721e2244a230dd33447a5f6f0896b014279..0c4879f0d4fe082a57b748592d126bf52a412291 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 @@ -34,9 +34,9 @@ sealed class CoronaTestQRCode : Parcelable, TestRegistrationRequest { data class RapidAntigen( val hash: RapidAntigenHash, val createdAt: Instant, - val firstName: String?, - val lastName: String?, - val dateOfBirth: LocalDate?, + val firstName: String? = null, + val lastName: String? = null, + val dateOfBirth: LocalDate? = null, ) : CoronaTestQRCode() { @IgnoredOnParcel diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestResultAvailableNotificationService.kt index b6e85bebc655114e7f1b5c25aea02dd645f9c43e..48685eab773107a54f0fa7cc209163e4adaa8108 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestResultAvailableNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/common/TestResultAvailableNotificationService.kt @@ -22,18 +22,19 @@ open class TestResultAvailableNotificationService( private val notificationHelper: GeneralNotifications, private val cwaSettings: CWASettings, private val notificationId: NotificationId, + private val logTag: String, ) { suspend fun showTestResultAvailableNotification(test: CoronaTest) { - Timber.d("showTestResultAvailableNotification(test=%s)", test) + Timber.tag(logTag).v("showTestResultAvailableNotification(test=%s)", test) if (foregroundState.isInForeground.first()) { - Timber.d("App in foreground, skipping notification.") + Timber.tag(logTag).d("App in foreground, skipping notification.") return } if (!cwaSettings.isNotificationsTestEnabled.value) { - Timber.i("Don't show test result available notification because user doesn't want to be informed") + Timber.tag(logTag).i("User has disabled test result notifications.") return } @@ -63,7 +64,7 @@ open class TestResultAvailableNotificationService( setContentIntent(pendingIntent) }.build() - Timber.i("Showing TestResultAvailable notification!") + Timber.tag(logTag).i("Showing test result notification($notificationId) for %s", test) notificationHelper.sendNotification( notificationId = notificationId, notification = notification, @@ -71,6 +72,7 @@ open class TestResultAvailableNotificationService( } fun cancelTestResultAvailableNotification() { + Timber.tag(logTag).i("Canceling test result notification($notificationId)") notificationHelper.cancelCurrentNotification(notificationId) } } 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 815401d6e2b1202aa515de4e046d378e0bfb3d0c..03dc9df0aa1f22fbc3a0feaec0263e6465d3b636 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 @@ -46,7 +46,9 @@ class PCRProcessor @Inject constructor( Timber.tag(TAG).d("create(data=%s)", request) request as CoronaTestQRCode.PCR - val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.qrCodeGUID) + val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.qrCodeGUID).also { + Timber.tag(TAG).d("Request %s gave us %s", request, it) + } testResultDataCollector.saveTestResultAnalyticsSettings(registrationData.testResult) // This saves received at @@ -72,9 +74,12 @@ class PCRProcessor @Inject constructor( ): PCRCoronaTest { analyticsKeySubmissionCollector.reset() - val testResult = response.testResult.validOrThrow() + val testResult = response.testResult.let { + Timber.tag(TAG).v("Raw test result $it") + testResultDataCollector.updatePendingTestResultReceivedTime(it) - testResultDataCollector.updatePendingTestResultReceivedTime(testResult) + it.toValidatedResult() + } if (testResult == PCR_POSITIVE) { analyticsKeySubmissionCollector.reportPositiveTestResultReceived() @@ -105,12 +110,12 @@ class PCRProcessor @Inject constructor( return test } - val newTestResult = submissionService.asyncRequestTestResult(test.registrationToken) - Timber.tag(TAG).d("Test result was %s", newTestResult) - - newTestResult.validOrThrow() + val newTestResult = submissionService.asyncRequestTestResult(test.registrationToken).let { + Timber.tag(TAG).d("Raw test result was %s", it) + testResultDataCollector.updatePendingTestResultReceivedTime(it) - testResultDataCollector.updatePendingTestResultReceivedTime(newTestResult) + it.toValidatedResult() + } if (newTestResult == PCR_POSITIVE) { analyticsKeySubmissionCollector.reportPositiveTestResultReceived() @@ -193,11 +198,11 @@ class PCRProcessor @Inject constructor( companion object { private val FINAL_STATES = setOf(PCR_POSITIVE, PCR_NEGATIVE, PCR_REDEEMED) - private const val TAG = "PCRProcessor" + internal const val TAG = "PCRProcessor" } } -private fun CoronaTestResult.validOrThrow(): CoronaTestResult { +private fun CoronaTestResult.toValidatedResult(): CoronaTestResult { val isValid = when (this) { PCR_OR_RAT_PENDING, PCR_NEGATIVE, @@ -212,6 +217,10 @@ private fun CoronaTestResult.validOrThrow(): CoronaTestResult { RAT_REDEEMED -> false } - if (!isValid) throw IllegalArgumentException("Invalid testResult $this") - return this + return if (isValid) { + this + } else { + Timber.tag(PCRProcessor.TAG).e("Server returned invalid PCR testresult $this") + PCR_INVALID + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/notification/PCRTestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/notification/PCRTestResultAvailableNotificationService.kt index d8d631cb6882dac0f50ed6f9592be76f620ec28c..1a2dd6872c13a09509dd74816e9bae3b6f8dbb1a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/notification/PCRTestResultAvailableNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/pcr/notification/PCRTestResultAvailableNotificationService.kt @@ -36,31 +36,43 @@ class PCRTestResultAvailableNotificationService @Inject constructor( navDeepLinkBuilderProvider, notificationHelper, cwaSettings, - NotificationConstants.PCR_TEST_RESULT_AVAILABLE_NOTIFICATION_ID + NotificationConstants.PCR_TEST_RESULT_AVAILABLE_NOTIFICATION_ID, + logTag = TAG, ) { + fun setup() { Timber.tag(TAG).d("setup() - PCRTestResultAvailableNotificationService") + @Suppress("RedundantLambdaArrow") coronaTestRepository.latestPCRT .onEach { _ -> + // We want the flow to trigger us, but not work with outdated data due to queue processing val test = coronaTestRepository.latestPCRT.first() + Timber.tag(TAG).v("PCR test change: %s", test) + if (test == null) { cancelTestResultAvailableNotification() return@onEach } - val alreadySent = test.isResultAvailableNotificationSent + val notSentYet = !test.isResultAvailableNotificationSent val isInteresting = INTERESTING_STATES.contains(test.testResult) - Timber.tag(TAG).v("alreadySent=$alreadySent, isInteresting=$isInteresting") + val isTestViewed = test.isViewed + Timber.tag(TAG).v("notSentYet=$notSentYet, isInteresting=$isInteresting, isTestViewed=$isTestViewed") - if (!alreadySent && isInteresting) { - coronaTestRepository.updateResultNotification(identifier = test.identifier, sent = true) - showTestResultAvailableNotification(test) - notificationHelper.cancelCurrentNotification( - NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID - ) - } else { - cancelTestResultAvailableNotification() + when { + notSentYet && isInteresting -> { + Timber.tag(TAG).d("Showing PCR test result notification.") + showTestResultAvailableNotification(test) + coronaTestRepository.updateResultNotification(identifier = test.identifier, sent = true) + notificationHelper.cancelCurrentNotification( + NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID + ) + } + isTestViewed -> { + Timber.tag(TAG).d("Canceling PCR test result notification.") + cancelTestResultAvailableNotification() + } } } .launchIn(appScope) 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/RapidAntigenProcessor.kt index 9454ce480a3b245046b45d89fa89be69281cf4bd..2a9d412af608b844ebea4b85bbc209f10e14171d 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/RapidAntigenProcessor.kt @@ -39,9 +39,14 @@ class RapidAntigenProcessor @Inject constructor( Timber.tag(TAG).d("create(data=%s)", request) request as CoronaTestQRCode.RapidAntigen - val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.registrationIdentifier) + val registrationData = submissionService.asyncRegisterDeviceViaGUID(request.registrationIdentifier).also { + Timber.tag(TAG).d("Request %s gave us %s", request, it) + } - val testResult = registrationData.testResult.validOrThrow() + val testResult = registrationData.testResult.let { + Timber.tag(TAG).v("Raw test result was %s", it) + it.toValidatedResult() + } val now = timeStamper.nowUTC @@ -81,8 +86,10 @@ class RapidAntigenProcessor @Inject constructor( return test } - val newTestResult = submissionService.asyncRequestTestResult(test.registrationToken) - Timber.tag(TAG).d("Test result was %s", newTestResult) + val newTestResult = submissionService.asyncRequestTestResult(test.registrationToken).let { + Timber.tag(TAG).v("Raw test result was %s", it) + it.toValidatedResult() + } test.copy( testResult = check60PlusDays(test, newTestResult), @@ -157,11 +164,11 @@ class RapidAntigenProcessor @Inject constructor( companion object { private val FINAL_STATES = setOf(RAT_POSITIVE, RAT_NEGATIVE, RAT_REDEEMED) - private const val TAG = "RapidAntigenProcessor" + internal const val TAG = "RapidAntigenProcessor" } } -private fun CoronaTestResult.validOrThrow(): CoronaTestResult { +private fun CoronaTestResult.toValidatedResult(): CoronaTestResult { val isValid = when (this) { PCR_OR_RAT_PENDING, RAT_PENDING, @@ -176,6 +183,10 @@ private fun CoronaTestResult.validOrThrow(): CoronaTestResult { PCR_REDEEMED -> false } - if (!isValid) throw IllegalArgumentException("Invalid testResult $this") - return this + return if (isValid) { + this + } else { + Timber.tag(RapidAntigenProcessor.TAG).e("Server returned invalid RapidAntigen testresult $this") + RAT_INVALID + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/notification/RATTestResultAvailableNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/notification/RATTestResultAvailableNotificationService.kt index b7d5a0886b296ad610d713b5f1c5c798093e775a..49fd8b04f991b4596609052462ea70fa4e9bb9aa 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/notification/RATTestResultAvailableNotificationService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/rapidantigen/notification/RATTestResultAvailableNotificationService.kt @@ -3,7 +3,6 @@ package de.rki.coronawarnapp.coronatest.type.rapidantigen.notification import android.content.Context import androidx.navigation.NavDeepLinkBuilder import de.rki.coronawarnapp.coronatest.CoronaTestRepository -import de.rki.coronawarnapp.coronatest.latestPCRT import de.rki.coronawarnapp.coronatest.latestRAT import de.rki.coronawarnapp.coronatest.server.CoronaTestResult import de.rki.coronawarnapp.coronatest.type.common.TestResultAvailableNotificationService @@ -38,30 +37,41 @@ class RATTestResultAvailableNotificationService @Inject constructor( notificationHelper, cwaSettings, NotificationConstants.RAT_TEST_RESULT_AVAILABLE_NOTIFICATION_ID, + logTag = TAG, ) { fun setup() { Timber.tag(TAG).d("setup() - RATTestResultAvailableNotificationService") - coronaTestRepository.latestPCRT + @Suppress("RedundantLambdaArrow") + coronaTestRepository.latestRAT .onEach { _ -> + // We want the flow to trigger us, but not work with outdated data due to queue processing val test = coronaTestRepository.latestRAT.first() + Timber.tag(TAG).v("RA test change: %s", test) + if (test == null) { cancelTestResultAvailableNotification() return@onEach } - val alreadySent = test.isResultAvailableNotificationSent + val notSentYet = !test.isResultAvailableNotificationSent val isInteresting = INTERESTING_STATES.contains(test.testResult) - Timber.tag(TAG).v("alreadySent=$alreadySent, isInteresting=$isInteresting") + val isTestViewed = test.isViewed + Timber.tag(TAG).v("notSentYet=$notSentYet, isInteresting=$isInteresting, isTestViewed=$isTestViewed") - if (!alreadySent && isInteresting) { - coronaTestRepository.updateResultNotification(identifier = test.identifier, sent = true) - showTestResultAvailableNotification(test) - notificationHelper.cancelCurrentNotification( - NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID - ) - } else { - cancelTestResultAvailableNotification() + when { + notSentYet && isInteresting -> { + Timber.tag(TAG).d("Showing RA test result notification.") + showTestResultAvailableNotification(test) + coronaTestRepository.updateResultNotification(identifier = test.identifier, sent = true) + notificationHelper.cancelCurrentNotification( + NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID + ) + } + isTestViewed -> { + Timber.tag(TAG).d("Canceling RA test result notification as it has already been viewed.") + cancelTestResultAvailableNotification() + } } } .launchIn(appScope) 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 2028ef505b309a63ec1c202f7f045812203b970e..9f07dc1959de84bd42eb0760ac77689f9abf301d 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 @@ -2,6 +2,17 @@ package de.rki.coronawarnapp.coronatest.type.pcr import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.values import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector @@ -21,6 +32,7 @@ import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import timber.log.Timber class PCRProcessorTest : BaseTest() { @MockK lateinit var timeStamper: TimeStamper @@ -31,15 +43,6 @@ class PCRProcessorTest : BaseTest() { private val nowUTC = Instant.parse("2021-03-15T05:45:00.000Z") - private var testQRRegistrationData = CoronaTestService.RegistrationData( - registrationToken = "qr-regtoken", - testResult = CoronaTestResult.PCR_POSITIVE, - ) - private var testTANRegistrationData = CoronaTestService.RegistrationData( - registrationToken = "tan-regtoken", - testResult = CoronaTestResult.PCR_POSITIVE, - ) - @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -47,9 +50,15 @@ class PCRProcessorTest : BaseTest() { every { timeStamper.nowUTC } returns nowUTC submissionService.apply { - coEvery { asyncRequestTestResult(any()) } answers { CoronaTestResult.PCR_OR_RAT_PENDING } - coEvery { asyncRegisterDeviceViaTAN(any()) } answers { testTANRegistrationData } - coEvery { asyncRegisterDeviceViaGUID(any()) } answers { testQRRegistrationData } + coEvery { asyncRequestTestResult(any()) } returns PCR_OR_RAT_PENDING + coEvery { asyncRegisterDeviceViaGUID(any()) } returns CoronaTestService.RegistrationData( + registrationToken = "regtoken-qr", + testResult = PCR_OR_RAT_PENDING, + ) + coEvery { asyncRegisterDeviceViaTAN(any()) } returns CoronaTestService.RegistrationData( + registrationToken = "regtoken-tan", + testResult = PCR_OR_RAT_PENDING, + ) } analyticsKeySubmissionCollector.apply { @@ -84,16 +93,85 @@ class PCRProcessorTest : BaseTest() { lastUpdatedAt = Instant.EPOCH, registeredAt = nowUTC, registrationToken = "regtoken", - testResult = CoronaTestResult.PCR_POSITIVE + testResult = PCR_POSITIVE ) - instance.pollServer(pcrTest).testResult shouldBe CoronaTestResult.PCR_OR_RAT_PENDING + instance.pollServer(pcrTest).testResult shouldBe PCR_OR_RAT_PENDING val past60DaysTest = pcrTest.copy( registeredAt = nowUTC.minus(Duration.standardDays(21)) ) - instance.pollServer(past60DaysTest).testResult shouldBe CoronaTestResult.PCR_REDEEMED + instance.pollServer(past60DaysTest).testResult shouldBe PCR_REDEEMED + } + + @Test + fun `registering a new test maps invalid results to INVALID state`() = runBlockingTest { + var registrationData = CoronaTestService.RegistrationData( + registrationToken = "regtoken", + testResult = PCR_OR_RAT_PENDING, + ) + coEvery { submissionService.asyncRegisterDeviceViaGUID(any()) } answers { registrationData } + + val instance = createInstance() + + val request = CoronaTestQRCode.PCR(qrCodeGUID = "guid") + + values().forEach { + registrationData = registrationData.copy(testResult = it) + when (it) { + PCR_OR_RAT_PENDING, + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> instance.create(request).testResult shouldBe it + + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> + instance.create(request).testResult shouldBe PCR_INVALID + } + } + } + + @Test + fun `polling maps invalid results to INVALID state`() = runBlockingTest { + var pollResult: CoronaTestResult = PCR_OR_RAT_PENDING + coEvery { submissionService.asyncRequestTestResult(any()) } answers { pollResult } + + val instance = createInstance() + + val pcrTest = PCRCoronaTest( + identifier = "identifier", + lastUpdatedAt = Instant.EPOCH, + registeredAt = nowUTC, + registrationToken = "regtoken", + testResult = PCR_POSITIVE + ) + + values().forEach { + pollResult = it + when (it) { + PCR_OR_RAT_PENDING, + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> { + Timber.v("Should NOT throw for $it") + instance.pollServer(pcrTest).testResult shouldBe it + } + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> { + Timber.v("Should throw for $it") + instance.pollServer(pcrTest).testResult shouldBe PCR_INVALID + } + } + } } // TANs are automatically positive, there is no test result available screen that should be reached 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 b2b3693be4be5f6d4bca10a5c0ed201df0116d87..76573d88bc5c0dbfe14a727593fa4b37f1e2e55a 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 @@ -1,6 +1,18 @@ package de.rki.coronawarnapp.coronatest.type.rapidantigen +import de.rki.coronawarnapp.coronatest.qrcode.CoronaTestQRCode import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_OR_RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.PCR_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_INVALID +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_NEGATIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_PENDING +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_POSITIVE +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.RAT_REDEEMED +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult.values import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.util.TimeStamper import io.kotest.matchers.shouldBe @@ -29,7 +41,15 @@ class RapidAntigenProcessorTest : BaseTest() { every { timeStamper.nowUTC } returns nowUTC submissionService.apply { - coEvery { asyncRequestTestResult(any()) } answers { CoronaTestResult.PCR_OR_RAT_PENDING } + coEvery { asyncRequestTestResult(any()) } returns PCR_OR_RAT_PENDING + coEvery { asyncRegisterDeviceViaGUID(any()) } returns CoronaTestService.RegistrationData( + registrationToken = "regtoken-qr", + testResult = PCR_OR_RAT_PENDING, + ) + coEvery { asyncRegisterDeviceViaTAN(any()) } returns CoronaTestService.RegistrationData( + registrationToken = "regtoken-tan", + testResult = PCR_OR_RAT_PENDING, + ) } } @@ -42,21 +62,88 @@ class RapidAntigenProcessorTest : BaseTest() { fun `if we receive a pending result 60 days after registration, we map to REDEEMED`() = runBlockingTest { val instance = createInstance() - val pcrTest = RACoronaTest( + val raTest = RACoronaTest( identifier = "identifier", lastUpdatedAt = Instant.EPOCH, registeredAt = nowUTC, registrationToken = "regtoken", - testResult = CoronaTestResult.RAT_POSITIVE, + testResult = RAT_POSITIVE, testedAt = Instant.EPOCH, ) - instance.pollServer(pcrTest).testResult shouldBe CoronaTestResult.PCR_OR_RAT_PENDING + instance.pollServer(raTest).testResult shouldBe PCR_OR_RAT_PENDING - val past60DaysTest = pcrTest.copy( + val past60DaysTest = raTest.copy( registeredAt = nowUTC.minus(Duration.standardDays(21)) ) - instance.pollServer(past60DaysTest).testResult shouldBe CoronaTestResult.RAT_REDEEMED + instance.pollServer(past60DaysTest).testResult shouldBe RAT_REDEEMED + } + + @Test + fun `registering a new test maps invalid results to INVALID state`() = runBlockingTest { + var registrationData = CoronaTestService.RegistrationData( + registrationToken = "regtoken", + testResult = PCR_OR_RAT_PENDING, + ) + coEvery { submissionService.asyncRegisterDeviceViaGUID(any()) } answers { registrationData } + + val instance = createInstance() + + val request = CoronaTestQRCode.RapidAntigen( + hash = "hash", + createdAt = Instant.EPOCH, + ) + + values().forEach { + registrationData = registrationData.copy(testResult = it) + when (it) { + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> instance.create(request).testResult shouldBe RAT_INVALID + + PCR_OR_RAT_PENDING, + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> instance.create(request).testResult shouldBe it + } + } + } + + @Test + fun `polling filters out invalid test result values`() = runBlockingTest { + var pollResult: CoronaTestResult = PCR_OR_RAT_PENDING + coEvery { submissionService.asyncRequestTestResult(any()) } answers { pollResult } + + val instance = createInstance() + + val raTest = RACoronaTest( + identifier = "identifier", + lastUpdatedAt = Instant.EPOCH, + registeredAt = nowUTC, + registrationToken = "regtoken", + testResult = RAT_POSITIVE, + testedAt = Instant.EPOCH, + ) + + values().forEach { + pollResult = it + when (it) { + PCR_NEGATIVE, + PCR_POSITIVE, + PCR_INVALID, + PCR_REDEEMED -> instance.pollServer(raTest).testResult shouldBe RAT_INVALID + + PCR_OR_RAT_PENDING, + RAT_PENDING, + RAT_NEGATIVE, + RAT_POSITIVE, + RAT_INVALID, + RAT_REDEEMED -> instance.pollServer(raTest).testResult shouldBe it + } + } } }