diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt index 6807bf6701e021461901e28736c8d2e2e7cd4e59..0fdb395a62ec5d5ca519f8d009b3ee5b9f8514d2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/CoronaTestResult.kt @@ -3,8 +3,22 @@ package de.rki.coronawarnapp.coronatest.server import com.google.gson.TypeAdapter import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter +import org.joda.time.Instant import org.json.JSONObject +data class CoronaTestResultResponse( + val coronaTestResult: CoronaTestResult, + val sampleCollectedAt: Instant? +) { + companion object { + fun fromResponse(response: VerificationApiV1.TestResultResponse) = + CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.fromInt(response.testResult), + sampleCollectedAt = response.sampleCollectedAt?.toLong()?.let { Instant.ofEpochSecond(it) } + ) + } +} + enum class CoronaTestResult(val value: Int) { /** * Pending (PCR test) or Pending (rapid antigen test) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt index 3a11d242906c2835821f7e7e241d39e54d5b332c..d40f59b3bcf406e02d62e147cbf308009781d85c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1.kt @@ -30,7 +30,8 @@ interface VerificationApiV1 { ) data class TestResultResponse( - @SerializedName("testResult") val testResult: Int + @SerializedName("testResult") val testResult: Int, + @SerializedName("sc") val sampleCollectedAt: Int? ) @POST("version/v1/testresult") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt index 78d80f3f1620b50164a26ca76d3f09e81609ec0a..30d580cf923d5e32277d5e2f1551fa31aafe1b47 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/server/VerificationServer.kt @@ -51,7 +51,7 @@ class VerificationServer @Inject constructor( suspend fun pollTestResult( token: RegistrationToken - ): CoronaTestResult = withContext(Dispatchers.IO) { + ): CoronaTestResultResponse = withContext(Dispatchers.IO) { Timber.tag(TAG).v("retrieveTestResults(token=%s)", token) val response = api.getTestResult( fake = "0", @@ -63,7 +63,8 @@ class VerificationServer @Inject constructor( ) Timber.tag(TAG).d("retrieveTestResults(token=%s) -> %s", token, response) - CoronaTestResult.fromInt(response.testResult) + + CoronaTestResultResponse.fromResponse(response) } suspend fun retrieveTan( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt index f47d859b5a06c9eacb1531b4c1bf331d1c6b279b..dcc41c608a1bd0c21caa2f8caf4e7055053601fc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/coronatest/type/CoronaTestService.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.coronatest.type -import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.deniability.NoiseScheduler import de.rki.coronawarnapp.playbook.Playbook @@ -13,7 +13,7 @@ class CoronaTestService @Inject constructor( private val noiseScheduler: NoiseScheduler, ) { - suspend fun asyncRequestTestResult(registrationToken: String): CoronaTestResult { + suspend fun asyncRequestTestResult(registrationToken: String): CoronaTestResultResponse { return playbook.testResult(registrationToken) } @@ -51,6 +51,6 @@ class CoronaTestService @Inject constructor( data class RegistrationData( val registrationToken: String, - val testResult: CoronaTestResult + val testResultResponse: CoronaTestResultResponse ) } 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 d866dca57e230c320f0322319a5b0c7f4a2e9676..6cfb2c39a82883a98b3b66dfd1a458f020e19966 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 @@ -50,7 +50,8 @@ class PCRProcessor @Inject constructor( Timber.tag(TAG).d("Request %s gave us %s", request, it) } - testResultDataCollector.saveTestResultAnalyticsSettings(registrationData.testResult) // This saves received at + // This saves received at + testResultDataCollector.saveTestResultAnalyticsSettings(registrationData.testResultResponse.coronaTestResult) return createCoronaTest(request, registrationData) } @@ -74,7 +75,7 @@ class PCRProcessor @Inject constructor( ): PCRCoronaTest { analyticsKeySubmissionCollector.reset() - val testResult = response.testResult.let { + val testResult = response.testResultResponse.coronaTestResult.let { Timber.tag(TAG).v("Raw test result $it") testResultDataCollector.updatePendingTestResultReceivedTime(it) @@ -118,7 +119,7 @@ class PCRProcessor @Inject constructor( } val newTestResult = try { - submissionService.asyncRequestTestResult(test.registrationToken).let { + submissionService.asyncRequestTestResult(test.registrationToken).coronaTestResult.let { Timber.tag(TAG).d("Raw test result was %s", it) testResultDataCollector.updatePendingTestResultReceivedTime(it) 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 6e353f9b996d7bc56855c91a13ebd4e1ef4e9351..126787cc04c00086d90af173e4303bc74ff6fcd2 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 @@ -61,6 +61,9 @@ data class RACoronaTest( @SerializedName("dateOfBirth") val dateOfBirth: LocalDate? = null, + @SerializedName("sampleCollectedAt") + val sampleCollectedAt: Instant? = null, + @Transient override val isProcessing: Boolean = false, @Transient override val lastError: Throwable? = null, ) : CoronaTest { @@ -68,8 +71,10 @@ data class RACoronaTest( override val type: CoronaTest.Type get() = CoronaTest.Type.RAPID_ANTIGEN - private fun isOutdated(nowUTC: Instant, testConfig: CoronaTestConfig) = - testedAt.plus(testConfig.coronaRapidAntigenTestParameters.hoursToDeemTestOutdated).isBefore(nowUTC) + private fun isOutdated(nowUTC: Instant, testConfig: CoronaTestConfig): Boolean { + val timeoutTime = sampleCollectedAt ?: testedAt + return timeoutTime.plus(testConfig.coronaRapidAntigenTestParameters.hoursToDeemTestOutdated).isBefore(nowUTC) + } fun getState(nowUTC: Instant, testConfig: CoronaTestConfig) = if (testResult == RAT_NEGATIVE && isOutdated(nowUTC, testConfig)) { 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 13654af995730337cf71d2b4e257b9dfda554354..941c71028c67d5652b536adcdc1d40e3e1e8524f 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 @@ -13,6 +13,7 @@ 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.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationServer import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTest @@ -45,11 +46,13 @@ class RapidAntigenProcessor @Inject constructor( Timber.tag(TAG).d("Request %s gave us %s", request, it) } - val testResult = registrationData.testResult.let { + val testResult = registrationData.testResultResponse.coronaTestResult.let { Timber.tag(TAG).v("Raw test result was %s", it) it.toValidatedResult() } + val sampleCollectedAt = registrationData.testResultResponse.sampleCollectedAt + val now = timeStamper.nowUTC return RACoronaTest( @@ -63,6 +66,7 @@ class RapidAntigenProcessor @Inject constructor( firstName = request.firstName, lastName = request.lastName, dateOfBirth = request.dateOfBirth, + sampleCollectedAt = sampleCollectedAt ) } @@ -99,12 +103,17 @@ class RapidAntigenProcessor @Inject constructor( val newTestResult = try { submissionService.asyncRequestTestResult(test.registrationToken).let { Timber.tag(TAG).v("Raw test result was %s", it) - it.toValidatedResult() + it.copy( + coronaTestResult = it.coronaTestResult.toValidatedResult() + ) } } catch (e: BadRequestException) { if (isOlderThan21Days) { Timber.tag(TAG).w("HTTP 400 error after 21 days, remapping to RAT_REDEEMED.") - RAT_REDEEMED + CoronaTestResultResponse( + coronaTestResult = RAT_REDEEMED, + sampleCollectedAt = null + ) } else { Timber.tag(TAG).v("Unexpected HTTP 400 error, rethrowing...") throw e @@ -112,10 +121,11 @@ class RapidAntigenProcessor @Inject constructor( } test.copy( - testResult = check60Days(test, newTestResult), - testResultReceivedAt = determineReceivedDate(test, newTestResult), + testResult = check60Days(test, newTestResult.coronaTestResult), + testResultReceivedAt = determineReceivedDate(test, newTestResult.coronaTestResult), lastUpdatedAt = nowUTC, - lastError = null + lastError = null, + sampleCollectedAt = newTestResult.sampleCollectedAt ) } catch (e: Exception) { Timber.tag(TAG).e(e, "Failed to poll server for %s", test) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt index a8e4a0367ce5d2ea4ea5c2c57de2fd9bebca5daa..3e2070c78cf0dc521a1288ae436f19242936b051 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/playbook/DefaultPlaybook.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.playbook -import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.coronatest.server.VerificationServer import de.rki.coronawarnapp.exception.TanPairingException @@ -28,7 +28,7 @@ class DefaultPlaybook @Inject constructor( override suspend fun initialRegistration( key: String, keyType: VerificationKeyType - ): Pair<String, CoronaTestResult> { + ): Pair<String, CoronaTestResultResponse> { Timber.i("[$uid] New Initial Registration Playbook") // real registration @@ -62,7 +62,7 @@ class DefaultPlaybook @Inject constructor( propagateException(registrationException, testResultException) } - override suspend fun testResult(registrationToken: String): CoronaTestResult { + override suspend fun testResult(registrationToken: String): CoronaTestResultResponse { Timber.i("[$uid] New Test Result Playbook") // real test result 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 6c62d63bdff3eff1a5e856f1d97962373430cef4..b631e93a17fc56cb105d878e1143ae5b72748f95 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 @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.playbook import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.server.protocols.external.exposurenotification.TemporaryExposureKeyExportOuterClass import de.rki.coronawarnapp.server.protocols.internal.SubmissionPayloadOuterClass.SubmissionPayload @@ -26,9 +27,9 @@ interface Playbook { suspend fun initialRegistration( key: String, keyType: VerificationKeyType - ): Pair<String, CoronaTestResult> + ): Pair<String, CoronaTestResultResponse> - suspend fun testResult(registrationToken: String): CoronaTestResult + suspend fun testResult(registrationToken: String): CoronaTestResultResponse suspend fun submit(data: SubmissionData) diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt index dd428bd56dace884e575ca41863a832297089bec..efa15930057abbbf9134e30acbfe70a9df3e0ff3 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationApiV1Test.kt @@ -124,7 +124,8 @@ class VerificationApiV1Test : BaseIOTest() { headerPadding = "testPadding", requestBody ) shouldBe VerificationApiV1.TestResultResponse( - testResult = 1 + testResult = 1, + sampleCollectedAt = null ) webServer.takeRequest(5, TimeUnit.SECONDS)!!.apply { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt index 86cf613364c9c51cb26871ef000dc051c3c9c9d1..6ac91f60fa275991116f8fbc52dc2f91017cfbc5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/server/VerificationServerTest.kt @@ -112,10 +112,13 @@ class VerificationServerTest : BaseIOTest() { registrationToken shouldBe "testRegistrationToken" requestPadding!!.length shouldBe 170 } - VerificationApiV1.TestResultResponse(testResult = 2) + VerificationApiV1.TestResultResponse(testResult = 2, sampleCollectedAt = null) } - server.pollTestResult("testRegistrationToken") shouldBe CoronaTestResult.PCR_POSITIVE + server.pollTestResult("testRegistrationToken") shouldBe CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_POSITIVE, + sampleCollectedAt = null + ) coVerify { verificationApi.getTestResult(any(), any(), any()) } } 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 4401dc012eeb67e776a812430b0211c16283f60d..d5dda05755932614abcc4a6cc540828f3a50a50a 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 @@ -13,6 +13,7 @@ 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.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.tan.CoronaTestTAN import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission.AnalyticsKeySubmissionCollector @@ -49,14 +50,23 @@ class PCRProcessorTest : BaseTest() { every { timeStamper.nowUTC } returns nowUTC submissionService.apply { - coEvery { asyncRequestTestResult(any()) } returns PCR_OR_RAT_PENDING + coEvery { asyncRequestTestResult(any()) } returns CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ) coEvery { asyncRegisterDeviceViaGUID(any()) } returns CoronaTestService.RegistrationData( registrationToken = "regtoken-qr", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ), ) coEvery { asyncRegisterDeviceViaTAN(any()) } returns CoronaTestService.RegistrationData( registrationToken = "regtoken-tan", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ), ) } @@ -104,7 +114,10 @@ class PCRProcessorTest : BaseTest() { fun `registering a new test maps invalid results to INVALID state`() = runBlockingTest { var registrationData = CoronaTestService.RegistrationData( registrationToken = "regtoken", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ) ) coEvery { submissionService.asyncRegisterDeviceViaGUID(any()) } answers { registrationData } @@ -113,7 +126,12 @@ class PCRProcessorTest : BaseTest() { val request = CoronaTestQRCode.PCR(qrCodeGUID = "guid") values().forEach { - registrationData = registrationData.copy(testResult = it) + registrationData = registrationData.copy( + testResultResponse = CoronaTestResultResponse( + coronaTestResult = it, + sampleCollectedAt = null, + ) + ) when (it) { PCR_OR_RAT_PENDING, PCR_NEGATIVE, @@ -134,7 +152,12 @@ class PCRProcessorTest : BaseTest() { @Test fun `polling maps invalid results to INVALID state`() = runBlockingTest { var pollResult: CoronaTestResult = PCR_OR_RAT_PENDING - coEvery { submissionService.asyncRequestTestResult(any()) } answers { pollResult } + coEvery { submissionService.asyncRequestTestResult(any()) } answers { + CoronaTestResultResponse( + coronaTestResult = pollResult, + sampleCollectedAt = null, + ) + } val instance = createInstance() @@ -185,7 +208,12 @@ class PCRProcessorTest : BaseTest() { @Test fun `polling is skipped if test is older than 21 days and state was already REDEEMED`() = runBlockingTest { - coEvery { submissionService.asyncRequestTestResult(any()) } answers { PCR_POSITIVE } + coEvery { submissionService.asyncRequestTestResult(any()) } answers { + CoronaTestResultResponse( + coronaTestResult = PCR_POSITIVE, + sampleCollectedAt = null, + ) + } val instance = createInstance() 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 b3287063eb1098b7de65538b526782e4c7402725..bb1b927aa18446a1580bba7769ffc68a995a2697 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 @@ -13,6 +13,7 @@ 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.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.exception.http.BadRequestException import de.rki.coronawarnapp.util.TimeStamper @@ -42,14 +43,23 @@ class RapidAntigenProcessorTest : BaseTest() { every { timeStamper.nowUTC } returns nowUTC submissionService.apply { - coEvery { asyncRequestTestResult(any()) } returns PCR_OR_RAT_PENDING + coEvery { asyncRequestTestResult(any()) } returns CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ) coEvery { asyncRegisterDeviceViaGUID(any()) } returns CoronaTestService.RegistrationData( registrationToken = "regtoken-qr", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ) ) coEvery { asyncRegisterDeviceViaTAN(any()) } returns CoronaTestService.RegistrationData( registrationToken = "regtoken-tan", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ) ) } } @@ -59,6 +69,28 @@ class RapidAntigenProcessorTest : BaseTest() { submissionService = submissionService, ) + @Test + fun `if a test result poll returns a sc set it on the test`() = runBlockingTest { + val instance = createInstance() + val raTest = RACoronaTest( + identifier = "identifier", + lastUpdatedAt = Instant.EPOCH, + registeredAt = nowUTC, + registrationToken = "regtoken", + testResult = RAT_POSITIVE, + testedAt = Instant.EPOCH, + ) + + (instance.pollServer(raTest) as RACoronaTest).sampleCollectedAt shouldBe null + + coEvery { submissionService.asyncRequestTestResult(any()) } returns CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = nowUTC, + ) + + (instance.pollServer(raTest) as RACoronaTest).sampleCollectedAt shouldBe nowUTC + } + @Test fun `if we receive a pending result 60 days after registration, we map to REDEEMED`() = runBlockingTest { val instance = createInstance() @@ -85,7 +117,10 @@ class RapidAntigenProcessorTest : BaseTest() { fun `registering a new test maps invalid results to INVALID state`() = runBlockingTest { var registrationData = CoronaTestService.RegistrationData( registrationToken = "regtoken", - testResult = PCR_OR_RAT_PENDING, + testResultResponse = CoronaTestResultResponse( + coronaTestResult = PCR_OR_RAT_PENDING, + sampleCollectedAt = null, + ), ) coEvery { submissionService.asyncRegisterDeviceViaGUID(any()) } answers { registrationData } @@ -97,7 +132,12 @@ class RapidAntigenProcessorTest : BaseTest() { ) values().forEach { - registrationData = registrationData.copy(testResult = it) + registrationData = registrationData.copy( + testResultResponse = CoronaTestResultResponse( + coronaTestResult = it, + sampleCollectedAt = null, + ) + ) when (it) { PCR_NEGATIVE, PCR_POSITIVE, @@ -117,7 +157,12 @@ class RapidAntigenProcessorTest : BaseTest() { @Test fun `polling filters out invalid test result values`() = runBlockingTest { var pollResult: CoronaTestResult = PCR_OR_RAT_PENDING - coEvery { submissionService.asyncRequestTestResult(any()) } answers { pollResult } + coEvery { submissionService.asyncRequestTestResult(any()) } answers { + CoronaTestResultResponse( + coronaTestResult = pollResult, + sampleCollectedAt = null, + ) + } val instance = createInstance() @@ -150,7 +195,12 @@ class RapidAntigenProcessorTest : BaseTest() { @Test fun `polling is skipped if test is older than 21 days and state was already REDEEMED`() = runBlockingTest { - coEvery { submissionService.asyncRequestTestResult(any()) } answers { RAT_POSITIVE } + coEvery { submissionService.asyncRequestTestResult(any()) } answers { + CoronaTestResultResponse( + coronaTestResult = RAT_POSITIVE, + sampleCollectedAt = null, + ) + } val instance = createInstance() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt index a53afe27f038d67591615b4e90e9765cc6f99c4e..8259af24f3f225b80ea9c03ec27e8066a023ed64 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/http/playbook/DefaultPlaybookTest.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.http.playbook import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.coronatest.server.VerificationServer import de.rki.coronawarnapp.exception.TanPairingException @@ -33,7 +34,10 @@ class DefaultPlaybookTest : BaseTest() { MockKAnnotations.init(this) coEvery { verificationServer.retrieveRegistrationToken(any(), any()) } returns "token" - coEvery { verificationServer.pollTestResult(any()) } returns CoronaTestResult.PCR_OR_RAT_PENDING + coEvery { verificationServer.pollTestResult(any()) } returns CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING, + sampleCollectedAt = null + ) coEvery { verificationServer.retrieveTanFake() } returns mockk() coEvery { verificationServer.retrieveTan(any()) } returns "tan" @@ -198,14 +202,20 @@ class DefaultPlaybookTest : BaseTest() { val expectedToken = "token" coEvery { verificationServer.retrieveRegistrationToken(any(), any()) } returns expectedToken val expectedResult = CoronaTestResult.PCR_OR_RAT_PENDING - coEvery { verificationServer.pollTestResult(expectedToken) } returns expectedResult + coEvery { verificationServer.pollTestResult(expectedToken) } returns CoronaTestResultResponse( + coronaTestResult = expectedResult, + sampleCollectedAt = null + ) coEvery { submissionServer.submitFakePayload() } throws TestException() val (registrationToken, testResult) = createPlaybook() .initialRegistration("key", VerificationKeyType.GUID) registrationToken shouldBe expectedToken - testResult shouldBe expectedResult + testResult shouldBe CoronaTestResultResponse( + coronaTestResult = expectedResult, + sampleCollectedAt = null + ) } @Test 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 3b74fd8ea4034bc728b7fd5ec7f1f77d479b2e82..5fa4e49b623b0b577a22c55fd0eb104ef66e14cb 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,6 +1,7 @@ package de.rki.coronawarnapp.service.submission import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.server.CoronaTestResultResponse import de.rki.coronawarnapp.coronatest.server.VerificationKeyType import de.rki.coronawarnapp.coronatest.type.CoronaTestService import de.rki.coronawarnapp.deniability.NoiseScheduler @@ -48,7 +49,12 @@ class SubmissionServiceTest : BaseTest() { fun registrationWithGUIDSucceeds() { coEvery { mockPlaybook.initialRegistration(guid, VerificationKeyType.GUID) - } returns (registrationToken to CoronaTestResult.PCR_OR_RAT_PENDING) + } returns ( + registrationToken to CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING, + sampleCollectedAt = null + ) + ) runBlocking { submissionService.asyncRegisterDeviceViaGUID(guid) @@ -63,7 +69,12 @@ class SubmissionServiceTest : BaseTest() { fun registrationWithTeleTANSucceeds() { coEvery { mockPlaybook.initialRegistration(any(), VerificationKeyType.TELETAN) - } returns (registrationToken to CoronaTestResult.PCR_OR_RAT_PENDING) + } returns ( + registrationToken to CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_OR_RAT_PENDING, + sampleCollectedAt = null + ) + ) runBlocking { submissionService.asyncRegisterDeviceViaTAN(tan) @@ -76,10 +87,16 @@ class SubmissionServiceTest : BaseTest() { @Test fun requestTestResultSucceeds() { - coEvery { mockPlaybook.testResult(registrationToken) } returns CoronaTestResult.PCR_NEGATIVE + coEvery { mockPlaybook.testResult(registrationToken) } returns CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_NEGATIVE, + sampleCollectedAt = null + ) runBlocking { - submissionService.asyncRequestTestResult(registrationToken) shouldBe CoronaTestResult.PCR_NEGATIVE + submissionService.asyncRequestTestResult(registrationToken) shouldBe CoronaTestResultResponse( + coronaTestResult = CoronaTestResult.PCR_NEGATIVE, + sampleCollectedAt = null, + ) } coVerify(exactly = 1) { mockPlaybook.testResult(registrationToken)