From ae3a063fbfb5e1eb70cb424ba5d31664a9cf1e61 Mon Sep 17 00:00:00 2001 From: Kolya Opahle <k.opahle@sap.com> Date: Thu, 27 May 2021 15:45:18 +0200 Subject: [PATCH] Rapid Antigen Tests: Retrieve Test Result Timestamp from Verification Server (EXPOSUREAPP-6865) (#3287) * Added polling of sampleCollectedAt to Rat test exchange * Fixed linting unit tests and formatting * Switched from result, instant pair to CoronaTestResultResponse data class for clarity * using .copy where appropriate * Fixed unittests * Looks weird to me but ktlint wants it Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Co-authored-by: Mohamed Metwalli <mohamed.metwalli@sap.com> --- .../coronatest/server/CoronaTestResult.kt | 14 ++++ .../coronatest/server/VerificationApiV1.kt | 3 +- .../coronatest/server/VerificationServer.kt | 5 +- .../coronatest/type/CoronaTestService.kt | 6 +- .../coronatest/type/pcr/PCRProcessor.kt | 7 +- .../type/rapidantigen/RACoronaTest.kt | 9 ++- .../rapidantigen/RapidAntigenProcessor.kt | 22 +++++-- .../coronawarnapp/playbook/DefaultPlaybook.kt | 6 +- .../de/rki/coronawarnapp/playbook/Playbook.kt | 5 +- .../server/VerificationApiV1Test.kt | 3 +- .../server/VerificationServerTest.kt | 7 +- .../coronatest/type/pcr/PCRProcessorTest.kt | 42 ++++++++++-- .../rapidantigen/RapidAntigenProcessorTest.kt | 64 +++++++++++++++++-- .../http/playbook/DefaultPlaybookTest.kt | 16 ++++- .../submission/SubmissionServiceTest.kt | 25 ++++++-- 15 files changed, 188 insertions(+), 46 deletions(-) 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 6807bf670..0fdb395a6 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 3a11d2429..d40f59b3b 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 78d80f3f1..30d580cf9 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 f47d859b5..dcc41c608 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 d866dca57..6cfb2c39a 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 6e353f9b9..126787cc0 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 13654af99..941c71028 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 a8e4a0367..3e2070c78 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 6c62d63bd..b631e93a1 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 dd428bd56..efa159300 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 86cf61336..6ac91f60f 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 4401dc012..d5dda0575 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 b3287063e..bb1b927aa 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 a53afe27f..8259af24f 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 3b74fd8ea..5fa4e49b6 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) -- GitLab