diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt index 15968158f9b4d89f681d127bba1cdfc97559bd9e..03594794e6724dd7f620ab2281dae3b4f7762fe9 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/api/ui/TestForAPIFragment.kt @@ -26,6 +26,7 @@ import com.google.zxing.integration.android.IntentIntegrator import com.google.zxing.integration.android.IntentResult import com.google.zxing.qrcode.QRCodeWriter import de.rki.coronawarnapp.R +import de.rki.coronawarnapp.appconfig.AppConfigProvider import de.rki.coronawarnapp.databinding.FragmentTestForAPIBinding import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.ExceptionCategory.INTERNAL @@ -65,6 +66,9 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), @Inject lateinit var viewModelFactory: CWAViewModelFactoryProvider.Factory @Inject lateinit var enfClient: ENFClient + + // TODO: This is ugly, remove when refactoring the fragment + @Inject lateinit var appConfigProvider: AppConfigProvider @Inject lateinit var riskLevelStorage: RiskLevelStorage private val vm: TestForApiFragmentViewModel by cwaViewModels { viewModelFactory } @@ -291,7 +295,8 @@ class TestForAPIFragment : Fragment(R.layout.fragment_test_for_a_p_i), try { // only testing implementation: this is used to wait for the broadcastreceiver of the OS / EN API enfClient.provideDiagnosisKeys( - googleFileList + googleFileList, + appConfigProvider.getAppConfig().diagnosisKeysDataMapping ) showToast("Provided ${appleKeyList.size} keys to Google API") } catch (e: Exception) { diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.bin b/Corona-Warn-App/src/main/assets/default_app_config_android.bin index 72e76a3f1f0b4b5fe7275c9d0052477df4b0a129..e1e0e0b2b339e4b247d14d3017298353efef2f1e 100644 Binary files a/Corona-Warn-App/src/main/assets/default_app_config_android.bin and b/Corona-Warn-App/src/main/assets/default_app_config_android.bin differ diff --git a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 index ce41e9e2b98f97be0e8e4fb9b2ccd4d7cce1bf6b..ce2ef522ef7e6c73a130ab5a84a47089c9e299e7 100644 --- a/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 +++ b/Corona-Warn-App/src/main/assets/default_app_config_android.sha256 @@ -1 +1 @@ -3713298c705ee867f0b12cd2a05bc6209442baa156d8e38e19856a3a6b91a48e \ No newline at end of file +665e82fa8d333eea36c5bda14f22f4519dc7d1a9dc890872ce8bc07880030bf1 \ No newline at end of file diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt index 80eadd589d6bf68fc19648513195ab5e36841036..cf72d970a6f1ea04e86bf7530fe85b156f976322 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/ExposureWindowRiskCalculationConfig.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import de.rki.coronawarnapp.server.protocols.internal.v2.AppConfigAndroid import de.rki.coronawarnapp.server.protocols.internal.v2.RiskCalculationParametersOuterClass @@ -13,6 +14,7 @@ interface ExposureWindowRiskCalculationConfig { List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping> val normalizedTimePerDayToRiskLevelMappingList: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping> + val diagnosisKeysDataMapping: DiagnosisKeysDataMapping interface Mapper { fun map(rawConfig: AppConfigAndroid.ApplicationConfigurationAndroid): ExposureWindowRiskCalculationConfig diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt index ab55b97ee827ef27a091e1575c0eae048e50ee7a..607c0d8d0ed2019165a2705693411f70c4ef9d7f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/appconfig/mapping/ExposureWindowRiskCalculationConfigMapper.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.appconfig.mapping +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import dagger.Reusable import de.rki.coronawarnapp.appconfig.ExposureWindowRiskCalculationConfig import de.rki.coronawarnapp.appconfig.internal.ApplicationConfigurationInvalidException @@ -18,6 +19,12 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : ) } + if (!rawConfig.hasDiagnosisKeysDataMapping()) { + throw ApplicationConfigurationInvalidException( + message = "Diagnosis Keys Data Mapping is missing" + ) + } + val riskCalculationParameters = rawConfig.riskCalculationParameters return ExposureWindowRiskCalculationContainer( @@ -34,10 +41,24 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : normalizedTimePerExposureWindowToRiskLevelMapping = riskCalculationParameters .normalizedTimePerEWToRiskLevelMappingList, normalizedTimePerDayToRiskLevelMappingList = riskCalculationParameters - .normalizedTimePerDayToRiskLevelMappingList + .normalizedTimePerDayToRiskLevelMappingList, + diagnosisKeysDataMapping = rawConfig.diagnosisKeysDataMapping() ) } + private fun AppConfigAndroid.ApplicationConfigurationAndroid.diagnosisKeysDataMapping(): + DiagnosisKeysDataMapping { + val diagnosisKeyDataMapping = this.diagnosisKeysDataMapping + return DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder() + .apply { + setDaysSinceOnsetToInfectiousness(diagnosisKeyDataMapping.daysSinceOnsetToInfectiousnessMap) + setInfectiousnessWhenDaysSinceOnsetMissing( + diagnosisKeysDataMapping.infectiousnessWhenDaysSinceOnsetMissing + ) + setReportTypeWhenMissing(diagnosisKeysDataMapping.reportTypeWhenMissing) + }.build() + } + data class ExposureWindowRiskCalculationContainer( override val minutesAtAttenuationFilters: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationFilter>, override val minutesAtAttenuationWeights: List<RiskCalculationParametersOuterClass.MinutesAtAttenuationWeight>, @@ -47,6 +68,7 @@ class ExposureWindowRiskCalculationConfigMapper @Inject constructor() : override val normalizedTimePerExposureWindowToRiskLevelMapping: List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>, override val normalizedTimePerDayToRiskLevelMappingList: - List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping> + List<RiskCalculationParametersOuterClass.NormalizedTimeToRiskLevelMapping>, + override val diagnosisKeysDataMapping: DiagnosisKeysDataMapping ) : ExposureWindowRiskCalculationConfig } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt index d04dc5137d1dc33c3eb31577ef33fade451f4594..8f34bcc8ced3d56d3c19ba2269e5a02e4be119b4 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/DownloadDiagnosisKeysTask.kt @@ -1,6 +1,7 @@ package de.rki.coronawarnapp.diagnosiskeys.download import de.rki.coronawarnapp.appconfig.AppConfigProvider +import de.rki.coronawarnapp.appconfig.ConfigData import de.rki.coronawarnapp.appconfig.ExposureDetectionConfig import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode import de.rki.coronawarnapp.environment.EnvironmentSetup @@ -63,7 +64,7 @@ class DownloadDiagnosisKeysTask @Inject constructor( throwIfCancelled() // RETRIEVE RISK SCORE PARAMETERS - val exposureConfig: ExposureDetectionConfig = appConfigProvider.getAppConfig() + val exposureConfig: ConfigData = appConfigProvider.getAppConfig() internalProgress.send(Progress.ApiSubmissionStarted) internalProgress.send(Progress.KeyFilesDownloadStarted) @@ -102,7 +103,10 @@ class DownloadDiagnosisKeysTask @Inject constructor( ) Timber.tag(TAG).d("Attempting submission to ENF") - val isSubmissionSuccessful = enfClient.provideDiagnosisKeys(availableKeyFiles) + val isSubmissionSuccessful = enfClient.provideDiagnosisKeys( + availableKeyFiles, + exposureConfig.diagnosisKeysDataMapping + ) Timber.tag(TAG).d("Diagnosis Keys provided (success=%s)", isSubmissionSuccessful) // EXPOSUREAPP-3878 write timestamp immediately after submission, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt index 587603da757c029bf13e0a553538130c9737fa96..4838e9ceeac1340f3a7bd07671e84e8b6dd712bc 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFClient.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.nearby +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import com.google.android.gms.nearby.exposurenotification.ExposureWindow import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker @@ -36,7 +37,10 @@ class ENFClient @Inject constructor( internal val internalClient: ExposureNotificationClient get() = googleENFClient - override suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean { + override suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + newDiagnosisKeysDataMapping: DiagnosisKeysDataMapping + ): Boolean { Timber.d("asyncProvideDiagnosisKeys(keyFiles=$keyFiles)") return if (keyFiles.isEmpty()) { @@ -45,7 +49,7 @@ class ENFClient @Inject constructor( } else { Timber.d("Forwarding %d key files to our DiagnosisKeyProvider.", keyFiles.size) exposureDetectionTracker.trackNewExposureDetection(UUID.randomUUID().toString()) - diagnosisKeyProvider.provideDiagnosisKeys(keyFiles) + diagnosisKeyProvider.provideDiagnosisKeys(keyFiles, newDiagnosisKeysDataMapping) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt index 1d4220ee0df1f0640c4d5587ddd56bfdbd669e77..cbf0c96de676d53d0f59d28aad11c4ea4ee121e5 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/ENFModule.kt @@ -9,6 +9,8 @@ import de.rki.coronawarnapp.nearby.modules.detectiontracker.DefaultExposureDetec import de.rki.coronawarnapp.nearby.modules.detectiontracker.ExposureDetectionTracker import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DefaultDiagnosisKeyProvider import de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider.DiagnosisKeyProvider +import de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper.DefaultDiagnosisKeysDataMapper +import de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper.DiagnosisKeysDataMapper import de.rki.coronawarnapp.nearby.modules.exposurewindow.DefaultExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.exposurewindow.ExposureWindowProvider import de.rki.coronawarnapp.nearby.modules.locationless.DefaultScanningSupport @@ -48,6 +50,11 @@ class ENFModule { fun exposureWindowProvider(exposureWindowProvider: DefaultExposureWindowProvider): ExposureWindowProvider = exposureWindowProvider + @Singleton + @Provides + fun diagnosisKeysDataMapper(diagnosisKeysDataMapper: DefaultDiagnosisKeysDataMapper): + DiagnosisKeysDataMapper = diagnosisKeysDataMapper + @Singleton @Provides fun calculationTracker(exposureDetectionTracker: DefaultExposureDetectionTracker): ExposureDetectionTracker = diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt index 2c5924ec9f54992538ea1c132b41c43fe5aece47..3f22ca8519ee4791d8639b77d794ed21ec81ab1c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProvider.kt @@ -1,9 +1,11 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider import com.google.android.gms.common.api.ApiException +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import com.google.android.gms.nearby.exposurenotification.DiagnosisKeyFileProvider import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient import de.rki.coronawarnapp.exception.reporting.ReportingConstants +import de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper.DiagnosisKeysDataMapper import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import timber.log.Timber import java.io.File @@ -17,10 +19,16 @@ import kotlin.coroutines.suspendCoroutine class DefaultDiagnosisKeyProvider @Inject constructor( private val enfVersion: ENFVersion, private val submissionQuota: SubmissionQuota, - private val enfClient: ExposureNotificationClient + private val enfClient: ExposureNotificationClient, + private val diagnosisKeysDataMapper: DiagnosisKeysDataMapper ) : DiagnosisKeyProvider { - override suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean { + override suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + newDiagnosisKeysDataMapping: DiagnosisKeysDataMapping + ): Boolean { + diagnosisKeysDataMapper.updateDiagnosisKeysDataMapping(newDiagnosisKeysDataMapping) + if (keyFiles.isEmpty()) { Timber.d("No key files submitted, returning early.") return true diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt index b3339619f5b4e96635d1326a43cfb8d1cc09951e..14e67c266290a30290e3bab27b0a8c413c8fc4e7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DiagnosisKeyProvider.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping import java.io.File interface DiagnosisKeyProvider { @@ -15,5 +16,8 @@ interface DiagnosisKeyProvider { * @return */ - suspend fun provideDiagnosisKeys(keyFiles: Collection<File>): Boolean + suspend fun provideDiagnosisKeys( + keyFiles: Collection<File>, + newDiagnosisKeysDataMapping: DiagnosisKeysDataMapping + ): Boolean } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..7f018e89cac0b2f5ecf64a4faacfe2779547fbfe --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapper.kt @@ -0,0 +1,64 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper + +import com.google.android.gms.common.api.ApiException +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationStatusCodes.FAILED_RATE_LIMITED +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +@Singleton +class DefaultDiagnosisKeysDataMapper @Inject constructor( + private val client: ExposureNotificationClient +) : DiagnosisKeysDataMapper { + private suspend fun getDiagnosisKeysDataMapping(): DiagnosisKeysDataMapping? = + suspendCoroutine { cont -> + client.diagnosisKeysDataMapping + .addOnSuccessListener { cont.resume(it) } + .addOnFailureListener { cont.resumeWithException(it) } + } + + private suspend fun setDiagnosisKeysDataMapping(diagnosisKeysDataMapping: DiagnosisKeysDataMapping) = + suspendCoroutine<Unit> { cont -> + client.setDiagnosisKeysDataMapping(diagnosisKeysDataMapping) + .addOnSuccessListener { cont.resume(Unit) } + .addOnFailureListener { cont.resumeWithException(it) } + } + + override suspend fun updateDiagnosisKeysDataMapping(newDiagnosisKeysDataMapping: DiagnosisKeysDataMapping) { + val currentDiagnosisKeysDataMapping = + try { + getDiagnosisKeysDataMapping() + } catch (e: Exception) { + Timber.e("Failed to get the current DiagnosisKeysDataMapping assuming none present") + null + } + + if (newDiagnosisKeysDataMapping.hasChanged(currentDiagnosisKeysDataMapping)) { + try { + Timber.i( + "Current DiagnosisKeysDataMapping: %s vs new: %s, applying new version.", + currentDiagnosisKeysDataMapping, + newDiagnosisKeysDataMapping + ) + setDiagnosisKeysDataMapping(newDiagnosisKeysDataMapping) + } catch (e: ApiException) { + if (e.statusCode == FAILED_RATE_LIMITED) { + Timber.e(e, "Failed to setDiagnosisKeysDataMapping due to rate limit ") + } else { + throw e + } + } + } + } + + companion object { + fun DiagnosisKeysDataMapping?.hasChanged(old: DiagnosisKeysDataMapping?): Boolean { + return old == null || old.hashCode() != hashCode() + } + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DiagnosisKeysDataMapper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DiagnosisKeysDataMapper.kt new file mode 100644 index 0000000000000000000000000000000000000000..99cdec102fa88402e49c66f18e62e9553d30858b --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DiagnosisKeysDataMapper.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper + +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping + +interface DiagnosisKeysDataMapper { + suspend fun updateDiagnosisKeysDataMapping(newDiagnosisKeysDataMapping: DiagnosisKeysDataMapping) +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt index c9459763b063371cb4d0630ac7b7ff0b483dd090..fa6cb5ee2c3502fbea1657096b9e12316febbd1e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/appconfig/sources/fallback/DefaultAppConfigSanityCheck.kt @@ -47,7 +47,7 @@ class DefaultAppConfigSanityCheck : BaseTest() { fun `current default matches checksum`() { val config = context.assets.open(configName).readBytes() val sha256 = context.assets.open(checkSumName).readBytes().toString(Charsets.UTF_8) - sha256 shouldBe "3713298c705ee867f0b12cd2a05bc6209442baa156d8e38e19856a3a6b91a48e" + sha256 shouldBe "665e82fa8d333eea36c5bda14f22f4519dc7d1a9dc890872ce8bc07880030bf1" config.toSHA256() shouldBe sha256 } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt index b24f8fc8964adf921e00389250ed379eb54ea4d1..39e4639e53407bb9b4e1066c2a203045f8f5c364 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/ENFClientTest.kt @@ -19,6 +19,7 @@ import io.mockk.coVerifySequence import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just +import io.mockk.mockk import io.mockk.verifySequence import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -45,7 +46,7 @@ class ENFClientTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any()) } returns true every { exposureDetectionTracker.trackNewExposureDetection(any()) } just Runs } @@ -75,19 +76,20 @@ class ENFClientTest : BaseTest() { val client = createClient() val keyFiles = listOf(File("test")) - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any()) } returns true runBlocking { - client.provideDiagnosisKeys(keyFiles) shouldBe true + client.provideDiagnosisKeys(keyFiles, mockk()) shouldBe true } - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns false + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any()) } returns false runBlocking { - client.provideDiagnosisKeys(keyFiles) shouldBe false + client.provideDiagnosisKeys(keyFiles, mockk()) shouldBe false } coVerify(exactly = 2) { diagnosisKeyProvider.provideDiagnosisKeys( - keyFiles + keyFiles, + any() ) } } @@ -97,13 +99,13 @@ class ENFClientTest : BaseTest() { val client = createClient() val keyFiles = emptyList<File>() - coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any()) } returns true + coEvery { diagnosisKeyProvider.provideDiagnosisKeys(any(), any()) } returns true runBlocking { - client.provideDiagnosisKeys(keyFiles) shouldBe true + client.provideDiagnosisKeys(keyFiles, mockk()) shouldBe true } coVerify(exactly = 0) { - diagnosisKeyProvider.provideDiagnosisKeys(any()) + diagnosisKeyProvider.provideDiagnosisKeys(any(), any()) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt index 3092fb5d3457283c591e17720f3efba28f1f880e..034c9d9e36c38cd3c0de667fcc78aa61fc4335c1 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeyprovider/DefaultDiagnosisKeyProviderTest.kt @@ -2,16 +2,20 @@ package de.rki.coronawarnapp.nearby.modules.diagnosiskeyprovider import com.google.android.gms.nearby.exposurenotification.DiagnosisKeyFileProvider import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper.DiagnosisKeysDataMapper import de.rki.coronawarnapp.nearby.modules.version.ENFVersion import de.rki.coronawarnapp.nearby.modules.version.OutdatedENFVersionException import io.kotest.matchers.shouldBe import io.mockk.Called import io.mockk.MockKAnnotations +import io.mockk.Runs import io.mockk.clearAllMocks import io.mockk.coEvery import io.mockk.coVerify import io.mockk.coVerifySequence import io.mockk.impl.annotations.MockK +import io.mockk.just +import io.mockk.mockk import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.AfterEach @@ -26,6 +30,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { @MockK lateinit var googleENFClient: ExposureNotificationClient @MockK lateinit var enfVersion: ENFVersion @MockK lateinit var submissionQuota: SubmissionQuota + @MockK lateinit var diagnosisKeysDataMapper: DiagnosisKeysDataMapper private val exampleKeyFiles = listOf(File("file1"), File("file2")) @@ -33,6 +38,8 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { fun setup() { MockKAnnotations.init(this) + coEvery { diagnosisKeysDataMapper.updateDiagnosisKeysDataMapping(any()) } just Runs + coEvery { submissionQuota.consumeQuota(any()) } returns true coEvery { googleENFClient.provideDiagnosisKeys(any<List<File>>()) } returns MockGMSTask.forValue(null) @@ -50,7 +57,8 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { private fun createProvider() = DefaultDiagnosisKeyProvider( enfVersion = enfVersion, submissionQuota = submissionQuota, - enfClient = googleENFClient + enfClient = googleENFClient, + diagnosisKeysDataMapper = diagnosisKeysDataMapper ) @Test @@ -63,7 +71,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { val provider = createProvider() assertThrows<OutdatedENFVersionException> { - runBlockingTest { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe false + runBlockingTest { provider.provideDiagnosisKeys(exampleKeyFiles, mockk()) } shouldBe false } coVerify { @@ -78,7 +86,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { val provider = createProvider() - runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe true + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles, mockk()) } shouldBe true coVerifySequence { submissionQuota.consumeQuota(1) @@ -92,7 +100,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { val provider = createProvider() - runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe true + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles, mockk()) } shouldBe true coVerifySequence { submissionQuota.consumeQuota(1) @@ -107,7 +115,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { val provider = createProvider() - runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles) } shouldBe true + runBlocking { provider.provideDiagnosisKeys(exampleKeyFiles, mockk()) } shouldBe true coVerifySequence { submissionQuota.consumeQuota(1) @@ -119,7 +127,7 @@ class DefaultDiagnosisKeyProviderTest : BaseTest() { fun `provide empty key list`() { val provider = createProvider() - runBlocking { provider.provideDiagnosisKeys(emptyList()) } shouldBe true + runBlocking { provider.provideDiagnosisKeys(emptyList(), mockk()) } shouldBe true coVerify { googleENFClient wasNot Called diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapperTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapperTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..188056cc3e4bc6e74515a34cc305ee1b268e1907 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/nearby/modules/diagnosiskeysdatamapper/DefaultDiagnosisKeysDataMapperTest.kt @@ -0,0 +1,138 @@ +package de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper + +import com.google.android.gms.nearby.exposurenotification.DiagnosisKeysDataMapping +import com.google.android.gms.nearby.exposurenotification.ExposureNotificationClient +import com.google.android.gms.nearby.exposurenotification.Infectiousness +import com.google.android.gms.nearby.exposurenotification.ReportType +import de.rki.coronawarnapp.nearby.modules.diagnosiskeysdatamapper.DefaultDiagnosisKeysDataMapper.Companion.hasChanged +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.impl.annotations.MockK +import io.mockk.verify +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.coroutines.runBlockingTest2 +import testhelpers.gms.MockGMSTask + +class DefaultDiagnosisKeysDataMapperTest : BaseTest() { + @MockK lateinit var googleENFClient: ExposureNotificationClient + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + } + + @AfterEach + fun teardown() { + clearAllMocks() + } + + private fun createMapper() = DefaultDiagnosisKeysDataMapper( + client = googleENFClient + ) + + @Test + fun `set mapping is invoked`() { + val firstMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.STANDARD, 1 to Infectiousness.HIGH)) + }.build() + + val secondMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.HIGH, 1 to Infectiousness.STANDARD)) + }.build() + + coEvery { googleENFClient.diagnosisKeysDataMapping } returns MockGMSTask.forValue(firstMapping) + coEvery { googleENFClient.setDiagnosisKeysDataMapping(any()) } returns MockGMSTask.forValue(null) + + val mapper = createMapper() + + runBlockingTest2 { + mapper.updateDiagnosisKeysDataMapping(secondMapping) + } + + verify { + googleENFClient.setDiagnosisKeysDataMapping(secondMapping) + } + } + + @Test + fun `set mapping is not invoked`() { + val firstMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.STANDARD, 1 to Infectiousness.HIGH)) + }.build() + + val secondMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.STANDARD, 1 to Infectiousness.HIGH)) + }.build() + + coEvery { googleENFClient.diagnosisKeysDataMapping } returns MockGMSTask.forValue(firstMapping) + coEvery { googleENFClient.setDiagnosisKeysDataMapping(any()) } returns MockGMSTask.forValue(null) + + val mapper = createMapper() + + runBlockingTest2 { + mapper.updateDiagnosisKeysDataMapping(secondMapping) + } + + verify(exactly = 0) { + googleENFClient.setDiagnosisKeysDataMapping(secondMapping) + } + } + + @Test + fun `mapping change detection works`() { + // Note that we cant create an empty mapping as the DiagnosisKeysDataMappingBuilder + // throws a IllegalArgumentException if one of the properties is missing + val nullMapping: DiagnosisKeysDataMapping? = null + val firstMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.STANDARD, 1 to Infectiousness.HIGH)) + }.build() + val secondMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf(0 to Infectiousness.HIGH, 1 to Infectiousness.STANDARD)) + }.build() + val thirdMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf()) + }.build() + val fourthMapping = DiagnosisKeysDataMapping.DiagnosisKeysDataMappingBuilder().apply { + setReportTypeWhenMissing(ReportType.CONFIRMED_TEST) + setInfectiousnessWhenDaysSinceOnsetMissing(Infectiousness.HIGH) + setDaysSinceOnsetToInfectiousness(mapOf()) + }.build() + + firstMapping.hasChanged(nullMapping) shouldBe true + firstMapping.hasChanged(secondMapping) shouldBe true + firstMapping.hasChanged(thirdMapping) shouldBe true + + secondMapping.hasChanged(nullMapping) shouldBe true + secondMapping.hasChanged(firstMapping) shouldBe true + secondMapping.hasChanged(thirdMapping) shouldBe true + + thirdMapping.hasChanged(nullMapping) shouldBe true + thirdMapping.hasChanged(firstMapping) shouldBe true + thirdMapping.hasChanged(secondMapping) shouldBe true + + nullMapping.hasChanged(nullMapping) shouldBe true + firstMapping.hasChanged(firstMapping) shouldBe false + secondMapping.hasChanged(secondMapping) shouldBe false + thirdMapping.hasChanged(thirdMapping) shouldBe false + thirdMapping.hasChanged(fourthMapping) shouldBe false + } +}