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 710f2aa34d3a977801404cc8a92521b6e1a85183..0f260941b4ef3aaeb0d857b98da5d9cf98ba00a0 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 @@ -42,13 +42,19 @@ data class PCRCoronaTest( @Transient override val lastError: Throwable? = null, ) : CoronaTest { + @Transient override val type: CoronaTest.Type = CoronaTest.Type.PCR + @Transient override val isPositive: Boolean = testResult == CoronaTestResult.PCR_POSITIVE + + @Transient override val isPending: Boolean = testResult == CoronaTestResult.PCR_OR_RAT_PENDING + @Transient override val isSubmissionAllowed: Boolean = isPositive && !isSubmitted + @Transient val state: State = when (testResult) { CoronaTestResult.PCR_OR_RAT_PENDING -> State.PENDING CoronaTestResult.PCR_NEGATIVE -> State.NEGATIVE 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 77fdd42ba6eb9117041059805d44c1c410cbbb3d..ca4c97568717588adcc54b5a34adca1abba2e5d4 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 @@ -55,6 +55,7 @@ data class RACoronaTest( @Transient override val lastError: Throwable? = null, ) : CoronaTest { + @Transient override val type: CoronaTest.Type = CoronaTest.Type.RAPID_ANTIGEN fun getState(nowUTC: Instant) = when (testResult) { @@ -66,10 +67,14 @@ data class RACoronaTest( else -> throw IllegalArgumentException("Invalid RAT test state $testResult") } + @Transient override val isPositive: Boolean = testResult == CoronaTestResult.RAT_POSITIVE + + @Transient override val isPending: Boolean = testResult == CoronaTestResult.PCR_OR_RAT_PENDING || testResult == CoronaTestResult.RAT_PENDING + @Transient override val isSubmissionAllowed: Boolean = isPositive && !isSubmitted enum class State { 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 new file mode 100644 index 0000000000000000000000000000000000000000..fd88abd78b86a4810c405563e3a0492c276c8077 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/coronatest/storage/CoronaTestStorageTest.kt @@ -0,0 +1,193 @@ +package de.rki.coronawarnapp.coronatest.storage + +import android.content.Context +import androidx.core.content.edit +import de.rki.coronawarnapp.coronatest.server.CoronaTestResult +import de.rki.coronawarnapp.coronatest.type.pcr.PCRCoronaTest +import de.rki.coronawarnapp.coronatest.type.rapidantigen.RACoronaTest +import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import org.joda.time.Instant +import org.joda.time.LocalDate +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.extensions.toComparableJsonPretty +import testhelpers.preferences.MockSharedPreferences +import java.io.IOException + +class CoronaTestStorageTest : BaseTest() { + @MockK lateinit var context: Context + private lateinit var mockPreferences: MockSharedPreferences + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + mockPreferences = MockSharedPreferences() + + every { + context.getSharedPreferences("coronatest_localdata", Context.MODE_PRIVATE) + } returns mockPreferences + } + + private fun createInstance() = CoronaTestStorage( + context = context, + baseGson = SerializationModule().baseGson() + ) + + private val pcrTest = PCRCoronaTest( + identifier = "identifier-pcr", + registeredAt = Instant.ofEpochMilli(1000), + registrationToken = "regtoken-pcr", + isSubmitted = true, + isViewed = true, + isAdvancedConsentGiven = true, + isJournalEntryCreated = false, + isResultAvailableNotificationSent = false, + testResult = CoronaTestResult.PCR_POSITIVE, + testResultReceivedAt = Instant.ofEpochMilli(2000) + ) + private val raTest = RACoronaTest( + identifier = "identifier-ra", + registeredAt = Instant.ofEpochMilli(1000), + registrationToken = "regtoken-ra", + isSubmitted = true, + isViewed = true, + isAdvancedConsentGiven = true, + isJournalEntryCreated = false, + isResultAvailableNotificationSent = false, + testResult = CoronaTestResult.RAT_POSITIVE, + testResultReceivedAt = Instant.ofEpochMilli(2000), + firstName = "firstname", + lastName = "lastname", + dateOfBirth = LocalDate.parse("2021-12-24"), + testedAt = Instant.ofEpochMilli(3000) + ) + + @Test + fun `init is sideeffect free`() { + createInstance() + } + + @Test + fun `storing empty set deletes data`() { + mockPreferences.edit { + putString("dontdeleteme", "test") + putString("coronatest.data.ra", "test") + putString("coronatest.data.pcr", "test") + } + createInstance().coronaTests = emptySet() + + mockPreferences.dataMapPeek.keys.single() shouldBe "dontdeleteme" + } + + @Test + fun `store only PCRT`() { + val instance = createInstance() + instance.coronaTests = setOf( + pcrTest.copy( + isProcessing = true, + lastError = IOException() + ) + ) + + val json = (mockPreferences.dataMapPeek["coronatest.data.pcr"] as String) + + json.toComparableJsonPretty() shouldBe """ + [ + { + "identifier": "identifier-pcr", + "registeredAt": 1000, + "registrationToken": "regtoken-pcr", + "isSubmitted": true, + "isViewed": true, + "isAdvancedConsentGiven": true, + "isJournalEntryCreated": false, + "isResultAvailableNotificationSent": false, + "testResultReceivedAt": 2000, + "testResult": 2 + } + ] + """.toComparableJsonPretty() + + instance.coronaTests.single() shouldBe pcrTest.copy( + lastError = null, + isProcessing = false + ) + } + + @Test + fun `store only RAT`() { + val instance = createInstance() + instance.coronaTests = setOf( + raTest.copy( + isProcessing = true, + lastError = IOException() + ) + ) + + val json = (mockPreferences.dataMapPeek["coronatest.data.ra"] as String) + + json.toComparableJsonPretty() shouldBe """ + [ + { + "identifier": "identifier-ra", + "registeredAt": 1000, + "registrationToken": "regtoken-ra", + "isSubmitted": true, + "isViewed": true, + "isAdvancedConsentGiven": true, + "isJournalEntryCreated": false, + "isResultAvailableNotificationSent": false, + "testResultReceivedAt": 2000, + "testResult": 7, + "testedAt": 3000, + "firstName": "firstname", + "lastName": "lastname", + "dateOfBirth": "2021-12-24" + } + ] + """.toComparableJsonPretty() + + instance.coronaTests.single() shouldBe raTest.copy( + lastError = null, + isProcessing = false + ) + } + + @Test + fun `store one of each`() { + val instance = createInstance() + instance.coronaTests = setOf(raTest, pcrTest) + + mockPreferences.contains("coronatest.data.ra") shouldBe true + mockPreferences.contains("coronatest.data.pcr") shouldBe true + + instance.coronaTests shouldBe setOf(raTest, pcrTest) + } + + @Test + fun `storing one and deleting the other`() { + val instance = createInstance() + + instance.coronaTests = setOf(raTest) + mockPreferences.contains("coronatest.data.ra") shouldBe true + mockPreferences.contains("coronatest.data.pcr") shouldBe false + + instance.coronaTests = setOf(pcrTest) + + mockPreferences.contains("coronatest.data.ra") shouldBe false + mockPreferences.contains("coronatest.data.pcr") shouldBe true + instance.coronaTests shouldBe setOf(pcrTest) + + instance.coronaTests = setOf(raTest) + + mockPreferences.contains("coronatest.data.ra") shouldBe true + mockPreferences.contains("coronatest.data.pcr") shouldBe false + instance.coronaTests shouldBe setOf(raTest) + } +} diff --git a/Corona-Warn-App/src/test/java/testhelpers/extensions/JsonExtensions.kt b/Corona-Warn-App/src/test/java/testhelpers/extensions/JsonExtensions.kt index f2cd8033abfb526121a95cba5ae241ebf8e715d7..679b6650e9c1b6b070fe11995232ac0710fb3cbf 100644 --- a/Corona-Warn-App/src/test/java/testhelpers/extensions/JsonExtensions.kt +++ b/Corona-Warn-App/src/test/java/testhelpers/extensions/JsonExtensions.kt @@ -2,6 +2,8 @@ package testhelpers.extensions import com.google.gson.Gson import com.google.gson.GsonBuilder +import com.google.gson.JsonArray +import com.google.gson.JsonElement import com.google.gson.JsonObject import io.kotest.assertions.assertionCounter import io.kotest.assertions.collectOrThrow @@ -9,15 +11,20 @@ import io.kotest.assertions.eq.eq import io.kotest.assertions.errorCollector import okhttp3.mockwebserver.MockResponse +private fun String.determineJsonType(): Class<out JsonElement> = when { + this.trimStart().startsWith("[") -> JsonArray::class.java + else -> JsonObject::class.java +} + fun String.toComparableJson() = try { - Gson().fromJson(this, JsonObject::class.java).toString() + Gson().fromJson(this, this.determineJsonType()).toString() } catch (e: Exception) { throw IllegalArgumentException("'$this' wasn't valid JSON") } fun String.toComparableJsonPretty(): String = try { val gson = GsonBuilder().setPrettyPrinting().create() - val obj = gson.fromJson(this, JsonObject::class.java) + val obj = gson.fromJson(this, this.determineJsonType()) gson.toJson(obj) } catch (e: Exception) { throw IllegalArgumentException("'$this' wasn't valid JSON")