Skip to content
Snippets Groups Projects
Unverified Commit d36678b8 authored by BMItter's avatar BMItter Committed by GitHub
Browse files

Improved preference handling / thread safety (DEV) (#3252)


* Change to HotDataFlow

* Created test data for value sets, Adjusted storage tests, Also save null values

* Adjusted repo test

* Only emit when content changed

* ktlint & sourcecheck

* Thread safety

* Return empty value set as default

* detekt and sourceclean

Co-authored-by: default avatarharambasicluka <64483219+harambasicluka@users.noreply.github.com>
parent 61fdae63
No related branches found
No related tags found
No related merge requests found
Showing with 262 additions and 215 deletions
package de.rki.coronawarnapp.vaccination.core.repository package de.rki.coronawarnapp.vaccination.core.repository
import androidx.annotation.VisibleForTesting
import dagger.Reusable import dagger.Reusable
import de.rki.coronawarnapp.util.coroutine.AppScope import de.rki.coronawarnapp.util.coroutine.AppScope
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.flow.HotDataFlow
import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
import de.rki.coronawarnapp.vaccination.core.server.valueset.emptyVaccinationValueSet
import de.rki.coronawarnapp.vaccination.core.server.valueset.isEmpty import de.rki.coronawarnapp.vaccination.core.server.valueset.isEmpty
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.plus
import timber.log.Timber import timber.log.Timber
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
...@@ -19,55 +26,65 @@ import javax.inject.Inject ...@@ -19,55 +26,65 @@ import javax.inject.Inject
class ValueSetsRepository @Inject constructor( class ValueSetsRepository @Inject constructor(
private val vaccinationServer: VaccinationServer, private val vaccinationServer: VaccinationServer,
private val valueSetsStorage: ValueSetsStorage, private val valueSetsStorage: ValueSetsStorage,
@AppScope private val scope: CoroutineScope @AppScope private val scope: CoroutineScope,
dispatcherProvider: DispatcherProvider
) { ) {
val latestValueSet: Flow<VaccinationValueSet> = valueSetsStorage.valueSet.flow.distinctUntilChanged() private fun Flow<VaccinationValueSet>.distinctUntilChangedByHash() = distinctUntilChangedBy { it.hashCode() }
fun triggerUpdateValueSet(languageCode: Locale) = scope.launch { private val internalData: HotDataFlow<VaccinationValueSet> = HotDataFlow(
loggingTag = TAG,
scope = scope,
coroutineContext = dispatcherProvider.IO,
sharingBehavior = SharingStarted.Lazily,
startValueProvider = {
valueSetsStorage.vaccinationValueSet.also { Timber.v("Loaded initial value set %s", it) }
}
)
init {
internalData.data
.distinctUntilChangedByHash()
.onStart { Timber.d("Observing value set") }
.onEach { valueSetsStorage.vaccinationValueSet = it }
.catch { Timber.e(it, "Storing new value set failed.") }
.launchIn(scope + dispatcherProvider.IO)
}
val latestValueSet: Flow<VaccinationValueSet> = internalData.data.distinctUntilChangedByHash()
fun triggerUpdateValueSet(languageCode: Locale) {
Timber.d("triggerUpdateValueSet(languageCode=%s)", languageCode) Timber.d("triggerUpdateValueSet(languageCode=%s)", languageCode)
internalData.updateAsync(
onUpdate = { getValueSetFromServer(languageCode = languageCode) ?: this },
onError = { Timber.e(it, "Updating value set failed") }
)
}
private suspend fun getValueSetFromServer(languageCode: Locale): VaccinationValueSet? {
Timber.v("getValueSetFromServer(languageCode=%s)", languageCode)
var valueSet = vaccinationServer.getVaccinationValueSets(languageCode = languageCode) var valueSet = vaccinationServer.getVaccinationValueSets(languageCode = languageCode)
if (valueSet == null && valueSetsStorage.valueSet.value.isEmpty()) { if (valueSet.isEmpty()) {
Timber.d( Timber.d(
"Got no value set from server for %s and local value set is empty... " + "Got no value set from server for %s and local value set is empty... Try fallback to value set for en",
"Try fallback to value set for en",
languageCode.language languageCode.language
) )
valueSet = vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) valueSet = vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH)
} }
valueSet return valueSet
.also { Timber.d("Value set is %s", it) } .also { Timber.v("New value set %s", it) }
?.let { newValueSet -> valueSetsStorage.valueSet.update { newValueSet.toStoredVaccinationValueSet() } }
} }
fun clear() { suspend fun clear() {
Timber.d("Clearing value sets") Timber.d("Clearing value sets")
vaccinationServer.clear() vaccinationServer.clear()
valueSetsStorage.clear() internalData.updateBlocking {
Timber.v("Resetting value set to an empty value set")
emptyVaccinationValueSet
}
} }
} }
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) private const val TAG = "ValueSetsRepository"
fun VaccinationValueSet.toStoredVaccinationValueSet(): ValueSetsStorage.StoredVaccinationValueSet =
ValueSetsStorage.StoredVaccinationValueSet(
languageCode = languageCode,
vp = vp.toStoredValueSet(),
mp = mp.toStoredValueSet(),
ma = ma.toStoredValueSet()
)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun VaccinationValueSet.ValueSet.toStoredValueSet(): ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet =
ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(
items = items.map { it.toStoredItem() }
)
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun VaccinationValueSet.ValueSet.Item.toStoredItem():
ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet.StoredItem =
ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet.StoredItem(
key = key,
displayText = displayText
)
...@@ -3,15 +3,15 @@ package de.rki.coronawarnapp.vaccination.core.repository.storage ...@@ -3,15 +3,15 @@ package de.rki.coronawarnapp.vaccination.core.repository.storage
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.annotation.VisibleForTesting
import androidx.core.content.edit
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import dagger.Reusable import dagger.Reusable
import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.preferences.FlowPreference
import de.rki.coronawarnapp.util.preferences.clearAndNotify
import de.rki.coronawarnapp.util.preferences.createFlowPreference
import de.rki.coronawarnapp.util.serialization.BaseGson import de.rki.coronawarnapp.util.serialization.BaseGson
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
import de.rki.coronawarnapp.vaccination.core.server.valueset.emptyVaccinationValueSet
import timber.log.Timber import timber.log.Timber
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
...@@ -26,25 +26,52 @@ class ValueSetsStorage @Inject constructor( ...@@ -26,25 +26,52 @@ class ValueSetsStorage @Inject constructor(
context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
} }
var valueSet: FlowPreference<StoredVaccinationValueSet> = prefs.createFlowPreference( var vaccinationValueSet: VaccinationValueSet
key = PKEY_VALUE_SETS_PREFIX, get() = getValueSet()
reader = FlowPreference.gsonReader(gson = gson, createEmptyValueSet()), set(value) = setValueSet(value)
writer = FlowPreference.gsonWriter(gson = gson)
)
fun clear() { private fun getValueSet(): VaccinationValueSet {
Timber.d("Clearing local storage") Timber.v("Loading value set")
prefs.clearAndNotify() val valueSetString = prefs.getString(PKEY_VALUE_SETS_PREFIX, null)
return when (valueSetString != null) {
true -> gson.fromJson(valueSetString, StoredVaccinationValueSet::class.java)
else -> emptyVaccinationValueSet
}.also { loaded -> Timber.v("Loaded value set %s", loaded) }
} }
private fun createEmptyValueSet() = StoredVaccinationValueSet( private fun setValueSet(value: VaccinationValueSet) {
languageCode = Locale.ENGLISH, Timber.v("Saving value set %s", value)
vp = StoredVaccinationValueSet.StoredValueSet(items = emptyList()), prefs.edit {
mp = StoredVaccinationValueSet.StoredValueSet(items = emptyList()), val json = gson.toJson(value.toStoredVaccinationValueSet(), StoredVaccinationValueSet::class.java)
ma = StoredVaccinationValueSet.StoredValueSet(items = emptyList()) Timber.v("Writing %s to prefs", json)
) putString(PKEY_VALUE_SETS_PREFIX, json)
}
}
@VisibleForTesting
fun VaccinationValueSet.toStoredVaccinationValueSet(): StoredVaccinationValueSet =
StoredVaccinationValueSet(
languageCode = languageCode,
vp = vp.toStoredValueSet(),
mp = mp.toStoredValueSet(),
ma = ma.toStoredValueSet()
)
@VisibleForTesting
fun VaccinationValueSet.ValueSet.toStoredValueSet(): StoredVaccinationValueSet.StoredValueSet =
StoredVaccinationValueSet.StoredValueSet(
items = items.map { it.toStoredItem() }
)
@VisibleForTesting
fun VaccinationValueSet.ValueSet.Item.toStoredItem():
StoredVaccinationValueSet.StoredValueSet.StoredItem =
StoredVaccinationValueSet.StoredValueSet.StoredItem(
key = key,
displayText = displayText
)
@Keep @VisibleForTesting
data class StoredVaccinationValueSet( data class StoredVaccinationValueSet(
@SerializedName("languageCode") override val languageCode: Locale, @SerializedName("languageCode") override val languageCode: Locale,
@SerializedName("vp") override val vp: StoredValueSet, @SerializedName("vp") override val vp: StoredValueSet,
......
...@@ -19,3 +19,12 @@ data class DefaultVaccinationValueSet( ...@@ -19,3 +19,12 @@ data class DefaultVaccinationValueSet(
) : VaccinationValueSet.ValueSet.Item ) : VaccinationValueSet.ValueSet.Item
} }
} }
internal val emptyVaccinationValueSet: VaccinationValueSet by lazy {
DefaultVaccinationValueSet(
languageCode = Locale.ENGLISH,
vp = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList()),
mp = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList()),
ma = DefaultVaccinationValueSet.DefaultValueSet(items = emptyList())
)
}
...@@ -24,6 +24,6 @@ fun VaccinationValueSet.getDisplayText(key: String): String? = ...@@ -24,6 +24,6 @@ fun VaccinationValueSet.getDisplayText(key: String): String? =
fun VaccinationValueSet.ValueSet.getDisplayText(key: String): String? = items.find { key == it.key }?.displayText fun VaccinationValueSet.ValueSet.getDisplayText(key: String): String? = items.find { key == it.key }?.displayText
fun VaccinationValueSet.isEmpty(): Boolean = vp.isEmpty() && mp.isEmpty() && ma.isEmpty() fun VaccinationValueSet?.isEmpty(): Boolean = (this == null) || (vp.isEmpty() && mp.isEmpty() && ma.isEmpty())
fun VaccinationValueSet.ValueSet.isEmpty(): Boolean = items.isEmpty() fun VaccinationValueSet.ValueSet.isEmpty(): Boolean = items.isEmpty()
package de.rki.coronawarnapp.vaccination.core
import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage
import de.rki.coronawarnapp.vaccination.core.server.valueset.DefaultVaccinationValueSet
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet
import io.kotest.matchers.shouldBe
import java.util.Locale
object ValueSetTestData {
val vpItemDe = "1119305005" to "Impfstoff-Name"
val mpItemDe = "EU/1/21/1529" to "Arzneimittel-Name"
val maItemDe = "ORG-100001699" to "Hersteller-Name"
val vpItemEn = vpItemDe.copy(second = "Vaccine-Name")
val mpItemEn = mpItemDe.copy(second = "MedicalProduct-Name")
val maItemEn = maItemDe.copy(second = "Manufactorer-Name")
val storedValueSetDe = ValueSetsStorage.StoredVaccinationValueSet(
languageCode = Locale.GERMAN,
vp = createStoredValueSet(vpItemDe),
mp = createStoredValueSet(mpItemDe),
ma = createStoredValueSet(maItemDe)
)
val storedValueSetEn = ValueSetsStorage.StoredVaccinationValueSet(
languageCode = Locale.ENGLISH,
vp = createStoredValueSet(vpItemEn),
mp = createStoredValueSet(mpItemEn),
ma = createStoredValueSet(maItemEn)
)
val emptyStoredValueSet = ValueSetsStorage.StoredVaccinationValueSet(
languageCode = Locale.ENGLISH,
vp = ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(items = emptyList()),
mp = ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(items = emptyList()),
ma = ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(items = emptyList())
)
val valueSetDe = DefaultVaccinationValueSet(
languageCode = Locale.GERMAN,
vp = createValueSet(vpItemDe),
mp = createValueSet(mpItemDe),
ma = createValueSet(maItemDe)
)
val valueSetEn = DefaultVaccinationValueSet(
languageCode = Locale.ENGLISH,
vp = createValueSet(vpItemEn),
mp = createValueSet(mpItemEn),
ma = createValueSet(maItemEn)
)
val emptyValueSetEn = emptyStoredValueSet
fun createStoredValueSet(keyText: Pair<String, String>): ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet {
val item = ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet.StoredItem(
key = keyText.first,
displayText = keyText.second
)
return ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(items = listOf(item))
}
fun createValueSet(keyText: Pair<String, String>): DefaultVaccinationValueSet.DefaultValueSet {
val item = DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = keyText.first,
displayText = keyText.second
)
return DefaultVaccinationValueSet.DefaultValueSet(items = listOf(item))
}
}
fun VaccinationValueSet.validateValues(v2: VaccinationValueSet) {
languageCode shouldBe v2.languageCode
vp.validateValues(v2.vp)
mp.validateValues(v2.mp)
ma.validateValues(v2.ma)
}
fun VaccinationValueSet.ValueSet.validateValues(v2: VaccinationValueSet.ValueSet) {
items.forEachIndexed { index, item1 ->
val item2 = v2.items[index]
item1.key shouldBe item2.key
item1.displayText shouldBe item2.displayText
}
}
package de.rki.coronawarnapp.vaccination.core.repository package de.rki.coronawarnapp.vaccination.core.repository
import de.rki.coronawarnapp.util.preferences.FlowPreference import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.emptyValueSetEn
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.valueSetDe
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.valueSetEn
import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage import de.rki.coronawarnapp.vaccination.core.repository.storage.ValueSetsStorage
import de.rki.coronawarnapp.vaccination.core.server.valueset.DefaultVaccinationValueSet
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationServer
import de.rki.coronawarnapp.vaccination.core.server.valueset.VaccinationValueSet import de.rki.coronawarnapp.vaccination.core.validateValues
import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Ordering import io.mockk.Ordering
import io.mockk.coEvery import io.mockk.coEvery
...@@ -14,13 +14,13 @@ import io.mockk.every ...@@ -14,13 +14,13 @@ import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.runs import io.mockk.runs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import testhelpers.BaseTest import testhelpers.BaseTest
import testhelpers.preferences.mockFlowPreference import testhelpers.TestDispatcherProvider
import testhelpers.coroutines.runBlockingTest2
import java.util.Locale import java.util.Locale
class ValueSetsRepositoryTest : BaseTest() { class ValueSetsRepositoryTest : BaseTest() {
...@@ -28,124 +28,68 @@ class ValueSetsRepositoryTest : BaseTest() { ...@@ -28,124 +28,68 @@ class ValueSetsRepositoryTest : BaseTest() {
@MockK lateinit var vaccinationServer: VaccinationServer @MockK lateinit var vaccinationServer: VaccinationServer
@MockK lateinit var valueSetsStorage: ValueSetsStorage @MockK lateinit var valueSetsStorage: ValueSetsStorage
private val testScope = TestCoroutineScope() private val testDispatcherProvider = TestDispatcherProvider()
private val emptyValueSetEN = createValueSet(languageCode = Locale.ENGLISH)
private val valueSetPref: FlowPreference<ValueSetsStorage.StoredVaccinationValueSet> =
mockFlowPreference(emptyValueSetEN.toStoredVaccinationValueSet())
private val valueSetEN = createValueSet(
languageCode = Locale.ENGLISH,
vpItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "1119305005",
displayText = "Vaccine-Name"
)
),
mpItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "EU/1/21/1529",
displayText = "MedicalProduct-Name"
)
),
maItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "ORG-100001699",
displayText = "Manufactorer-Name"
)
)
)
private val valueSetDE = createValueSet(
languageCode = Locale.GERMAN,
vpItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "1119305005",
displayText = "Impfstoff-Name"
)
),
mpItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "EU/1/21/1529",
displayText = "Arzneimittel-Name"
)
),
maItems = listOf(
DefaultVaccinationValueSet.DefaultValueSet.DefaultItem(
key = "ORG-100001699",
displayText = "Hersteller-Name"
)
)
)
private fun createValueSet(
languageCode: Locale,
vpItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList(),
mpItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList(),
maItems: List<DefaultVaccinationValueSet.DefaultValueSet.DefaultItem> = emptyList()
) = DefaultVaccinationValueSet(
languageCode = languageCode,
vp = DefaultVaccinationValueSet.DefaultValueSet(items = vpItems),
mp = DefaultVaccinationValueSet.DefaultValueSet(items = mpItems),
ma = DefaultVaccinationValueSet.DefaultValueSet(items = maItems)
)
private fun createInstance() = ValueSetsRepository( private fun createInstance(scope: CoroutineScope) = ValueSetsRepository(
vaccinationServer = vaccinationServer, vaccinationServer = vaccinationServer,
valueSetsStorage = valueSetsStorage, valueSetsStorage = valueSetsStorage,
scope = testScope scope = scope,
dispatcherProvider = testDispatcherProvider
) )
@BeforeEach @BeforeEach
fun setUp() { fun setUp() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
coEvery { vaccinationServer.getVaccinationValueSets(any()) } returns null coEvery { vaccinationServer.getVaccinationValueSets(any()) } returns null
coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) } returns valueSetEN coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) } returns valueSetEn
coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) } returns valueSetDE coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) } returns valueSetDe
every { vaccinationServer.clear() } just runs every { vaccinationServer.clear() } just runs
every { valueSetsStorage.valueSet } returns valueSetPref every { valueSetsStorage.vaccinationValueSet = any() } just runs
every { valueSetsStorage.clear() } just runs every { valueSetsStorage.vaccinationValueSet } returns emptyValueSetEn
} }
@Test @Test
fun `successful update for de`() = runBlockingTest { fun `successful update for de`() = runBlockingTest2(ignoreActive = true) {
createInstance().run { createInstance(this).run {
triggerUpdateValueSet(languageCode = Locale.GERMAN) triggerUpdateValueSet(languageCode = Locale.GERMAN)
latestValueSet.first() latestValueSet.first()
}.also { it.validateValues(valueSetDE) } }.also { it.validateValues(valueSetDe) }
coVerify(exactly = 1) { coVerify {
vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN)
valueSetsStorage.vaccinationValueSet = valueSetDe
} }
coVerify(exactly = 0) { coVerify(exactly = 0) {
vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH)
valueSetsStorage.vaccinationValueSet = valueSetEn
} }
} }
@Test @Test
fun `fallback to en`() = runBlockingTest { fun `fallback to en`() = runBlockingTest2(ignoreActive = true) {
createInstance().run { createInstance(this).run {
triggerUpdateValueSet(languageCode = Locale.FRENCH) triggerUpdateValueSet(languageCode = Locale.FRENCH)
latestValueSet.first() latestValueSet.first()
}.also { it.validateValues(valueSetEN) } }.also { it.validateValues(valueSetEn) }
coVerify(ordering = Ordering.ORDERED) { coVerify(ordering = Ordering.ORDERED) {
vaccinationServer.getVaccinationValueSets(languageCode = Locale.FRENCH) vaccinationServer.getVaccinationValueSets(languageCode = Locale.FRENCH)
vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH)
valueSetsStorage.vaccinationValueSet = valueSetEn
} }
} }
@Test @Test
fun `server returns nothing`() = runBlockingTest { fun `server returns nothing`() = runBlockingTest2(ignoreActive = true) {
coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) } returns null coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) } returns null
coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) } returns null coEvery { vaccinationServer.getVaccinationValueSets(languageCode = Locale.ENGLISH) } returns null
createInstance().run { createInstance(this).run {
triggerUpdateValueSet(languageCode = Locale.GERMAN) triggerUpdateValueSet(languageCode = Locale.GERMAN)
latestValueSet.first() latestValueSet.first()
}.also { it.validateValues(emptyValueSetEN) } }.also { it.validateValues(emptyValueSetEn) }
coVerify(ordering = Ordering.ORDERED) { coVerify(ordering = Ordering.ORDERED) {
vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN) vaccinationServer.getVaccinationValueSets(languageCode = Locale.GERMAN)
...@@ -154,37 +98,16 @@ class ValueSetsRepositoryTest : BaseTest() { ...@@ -154,37 +98,16 @@ class ValueSetsRepositoryTest : BaseTest() {
} }
@Test @Test
fun `mapping is to stored version is correct`() { fun `clear data of server and local storage`() = runBlockingTest2(ignoreActive = true) {
valueSetDE.also { it.toStoredVaccinationValueSet().validateValues(it) } createInstance(this).run {
valueSetEN.also { it.toStoredVaccinationValueSet().validateValues(it) }
emptyValueSetEN.also { it.toStoredVaccinationValueSet().validateValues(it) }
}
@Test
fun `clear data of server and local storage`() {
createInstance().run {
clear() clear()
coVerify(exactly = 1) { latestValueSet.first().validateValues(emptyValueSetEn)
coVerify {
vaccinationServer.clear() vaccinationServer.clear()
valueSetsStorage.clear() valueSetsStorage.vaccinationValueSet = emptyValueSetEn
} }
} }
} }
private fun VaccinationValueSet.validateValues(v2: VaccinationValueSet) {
languageCode shouldBe v2.languageCode
vp.validateValues(v2.vp)
mp.validateValues(v2.mp)
ma.validateValues(v2.ma)
}
private fun VaccinationValueSet.ValueSet.validateValues(v2: VaccinationValueSet.ValueSet) {
items.forEachIndexed { index, item1 ->
val item2 = v2.items[index]
item1.key shouldBe item2.key
item1.displayText shouldBe item2.displayText
}
}
} }
...@@ -2,17 +2,21 @@ package de.rki.coronawarnapp.vaccination.core.repository.storage ...@@ -2,17 +2,21 @@ package de.rki.coronawarnapp.vaccination.core.repository.storage
import android.content.Context import android.content.Context
import de.rki.coronawarnapp.util.serialization.SerializationModule import de.rki.coronawarnapp.util.serialization.SerializationModule
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.emptyStoredValueSet
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.emptyValueSetEn
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.storedValueSetDe
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.storedValueSetEn
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.valueSetDe
import de.rki.coronawarnapp.vaccination.core.ValueSetTestData.valueSetEn
import de.rki.coronawarnapp.vaccination.core.validateValues
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.every import io.mockk.every
import io.mockk.impl.annotations.MockK import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runBlockingTest
import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import testhelpers.BaseTest import testhelpers.BaseTest
import testhelpers.preferences.MockSharedPreferences import testhelpers.preferences.MockSharedPreferences
import java.util.Locale
class ValueSetsStorageTest : BaseTest() { class ValueSetsStorageTest : BaseTest() {
...@@ -21,28 +25,6 @@ class ValueSetsStorageTest : BaseTest() { ...@@ -21,28 +25,6 @@ class ValueSetsStorageTest : BaseTest() {
private val gson = SerializationModule().baseGson() private val gson = SerializationModule().baseGson()
private val storedValueSetDE = ValueSetsStorage.StoredVaccinationValueSet(
languageCode = Locale.GERMAN,
vp = createValueSet(key = "1119305005", displayText = "Impfstoff-Name"),
mp = createValueSet(key = "EU/1/21/1529", displayText = "Arzneimittel-Name"),
ma = createValueSet(key = "ORG-100001699", displayText = "Hersteller-Name")
)
private val storedValueSetEN = ValueSetsStorage.StoredVaccinationValueSet(
languageCode = Locale.ENGLISH,
vp = createValueSet(key = "1119305005", displayText = "Vaccine-Name"),
mp = createValueSet(key = "EU/1/21/1529", displayText = "MedicalProduct-Name"),
ma = createValueSet(key = "ORG-100001699", displayText = "Manufactorer-Name")
)
private fun createValueSet(key: String, displayText: String): ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet {
val item = ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet.StoredItem(
key = key,
displayText = displayText
)
return ValueSetsStorage.StoredVaccinationValueSet.StoredValueSet(items = listOf(item))
}
@BeforeEach @BeforeEach
fun setup() { fun setup() {
MockKAnnotations.init(this) MockKAnnotations.init(this)
...@@ -56,40 +38,42 @@ class ValueSetsStorageTest : BaseTest() { ...@@ -56,40 +38,42 @@ class ValueSetsStorageTest : BaseTest() {
) )
@Test @Test
fun `Default value is empty value set`() { fun `Default value is an empty value set`() {
createInstance().valueSet.value.run { createInstance().vaccinationValueSet.validateValues(emptyValueSetEn)
languageCode shouldBe Locale.ENGLISH
vp.items shouldBe emptyList()
mp.items shouldBe emptyList()
ma.items shouldBe emptyList()
}
} }
@Test @Test
fun `Clear resets value set`() { fun `Updates values`() {
createInstance().run { createInstance().run {
valueSet.update { storedValueSetDE } vaccinationValueSet = storedValueSetDe
clear() vaccinationValueSet shouldBe storedValueSetDe
valueSet.value.also { vaccinationValueSet = storedValueSetEn
it.languageCode shouldBe Locale.ENGLISH vaccinationValueSet shouldBe storedValueSetEn
it.vp.items shouldBe emptyList()
it.mp.items shouldBe emptyList()
it.ma.items shouldBe emptyList()
}
} }
} }
@Test @Test
fun `Updates values`() = runBlockingTest { fun `Check mapping is correct`() {
createInstance().valueSet.run { createInstance().run {
update { storedValueSetDE } storedValueSetDe.also { it.toStoredVaccinationValueSet() shouldBe it }
value shouldBe storedValueSetDE
flow.first() shouldBe storedValueSetDE storedValueSetEn.also { it.toStoredVaccinationValueSet() shouldBe it }
valueSetDe.also {
it.toStoredVaccinationValueSet() shouldBe storedValueSetDe
it.toStoredVaccinationValueSet().validateValues(it)
}
update { storedValueSetEN } valueSetEn.also {
value shouldBe storedValueSetEN it.toStoredVaccinationValueSet() shouldBe storedValueSetEn
flow.first() shouldBe storedValueSetEN it.toStoredVaccinationValueSet().validateValues(it)
}
emptyValueSetEn.also {
it.toStoredVaccinationValueSet() shouldBe emptyStoredValueSet
it.toStoredVaccinationValueSet().validateValues(it)
}
} }
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment