diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b37c1af039323178273fc1706e892aeaf56d730..1bbb37bc10983f38d05fad54c07c3fe738469210 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ Only start working on the Pull Request after the team assigned the issue to you If you have questions about one of the issues, please comment on them, and one of the maintainers will clarify. -We kindly ask you to follow the [Pull Request Checklist](#Pull-Request-Checklist) to ensure reviews can happen accordingly. +We kindly ask you to follow the [Pull Request Checklist](https://github.com/corona-warn-app/cwa-app-android/blob/master/.github/pull_request_template.md) to ensure reviews can happen accordingly. ## Contributing Code @@ -48,26 +48,6 @@ The following rule governs documentation contributions: * Contributions must be licensed under the same license as code, the [Apache 2.0 License](LICENSE) -## Pull Request Checklist - -* Branch from the dev branch and ensure it is up to date with the current dev branch before submitting your pull request. If it doesn't merge cleanly with dev, you may be asked to resolve the conflicts. Pull requests to master will be closed. - -* Commits should be as small as possible while ensuring that each commit is correct independently (i.e., each commit should compile and pass tests). - -* Pull requests must not contain compiled sources (already set by the default .gitignore) or binary files - -* Test your changes as thoroughly as possible before you commit them. Preferably, automate your test by unit/integration tests. If tested manually, provide information about the test scope in the PR description (e.g. “Test passed: Upgrade version from 0.42 to 0.42.23.â€). - -* Create _Work In Progress [WIP]_ pull requests only if you need clarification or an explicit review before you can continue your work item. - -* If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment, or you can ask for a review by contacting us via [email](mailto:corona-warn-app.opensource@sap.com). - -* Post review: - * If a review requires you to change your commit(s), please test the changes again. - * Amend the affected commit(s) and force push onto your branch. - * Set respective comments in your GitHub review to resolved. - * Create a general PR comment to notify the reviewers that your amendments are ready for another round of review. - ## Issues and Planning * We use GitHub issues to track bugs and enhancement requests. diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 578782e8fc9f7ecfd9839e7230f4b9eede6f6038..016749966dd1024ab83d10b6ab2ac44671f30014 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -39,7 +39,7 @@ android { applicationId 'de.rki.coronawarnapp' minSdkVersion 23 targetSdkVersion 29 - versionCode 45 + versionCode 47 versionName "1.5.0" testInstrumentationRunner "testhelpers.TestApplicationUIRunner" diff --git a/Corona-Warn-App/src/main/assets/terms_de.html b/Corona-Warn-App/src/main/assets/terms_de.html index 9bdd955479bf6c6252894a872d73e333a0383ac5..03458bc785114d5a938ab96335998898be4184d4 100644 --- a/Corona-Warn-App/src/main/assets/terms_de.html +++ b/Corona-Warn-App/src/main/assets/terms_de.html @@ -96,7 +96,7 @@ </p> <p> Sie sind für die Einhaltung dieser Nutzungsbedingungen auch dann - verantwortlich, wenn Sie das Endgerät, auf dem Sie die App installiert + verantwortlich, wenn Sie das Smartphone, auf dem Sie die App installiert haben, Dritten überlassen und diese die App verwenden. </p> <p> @@ -116,7 +116,7 @@ Die App nutzt das von Apple und Google bereitgestellte System für COVID-19-Begegnungsbenachrichtigungen („<strong>COVID-19-Benachrichtigungssystem</strong>“), das integraler - Bestandteil des Betriebssystems Ihres Endgeräts ist. Der genaue Name, die + Bestandteil des Betriebssystems Ihres Smartphones ist. Der genaue Name, die Bedienung und der Funktionsumfang des COVID-19-Benachrichtigungssystems können je nach Hersteller und Version Ihres Betriebssystems variieren. Das COVID-19-Benachrichtigungssystem ist kein Bestandteil der CWA-Dienste oder @@ -193,7 +193,7 @@ eventuellen Krankheitssymptomen an die CWA-Dienste übermitteln, damit andere Personen, die die offizielle Corona-App eines am länderübergreifenden Warnsystem teilnehmenden Landes nutzen, auf ihrem - eigenen Endgerät benachrichtigt werden können, falls sie mit Ihnen eine + eigenen Smartphone benachrichtigt werden können, falls sie mit Ihnen eine Risiko-Begegnung hatten. </p> <p> @@ -302,8 +302,8 @@ </strong> Die Risiko-Begegnung kann vom COVID-19-Benachrichtigungssystem beispielsweise zu einem Zeitpunkt aufgezeichnet worden sein, zu dem Sie - sich nicht in der Nähe Ihres Endgeräts aufgehalten haben oder während eine - andere Person Ihr Endgerät verwendet hat. Die Risiko-Begegnung kann auch + sich nicht in der Nähe Ihres Smartphones aufgehalten haben oder während eine + andere Person Ihr Smartphone verwendet hat. Die Risiko-Begegnung kann auch aufgrund bestehender Grenzen des COVID-19-Benachrichtigungssystems fälschlicherweise registriert worden sein (siehe unten Ziffer 8). </p> @@ -334,7 +334,7 @@ </ul> <p> o Die App kennt nicht alle Ihre Begegnungen mit anderen Personen, z.B. weil - andere Personen die App nicht verwenden, Sie Ihr Endgerät nicht immer bei + andere Personen die App nicht verwenden, Sie Ihr Smartphone nicht immer bei sich tragen oder die App nicht immer in Betrieb haben oder weil die Begegnungsaufzeichnung mit dem COVID-19-Benachrichtigungssystem gewissen Grenzen unterliegt (siehe unten Ziffer 8). @@ -454,39 +454,39 @@ <p> Bestimmte Funktionen der App setzen auf zentrale Dienste und Systeme auf, die über die CWA-Dienste zur Verfügung gestellt werden. Diese Funktionen - stehen daher nur zur Verfügung, wenn Ihr Endgerät über eine Datenverbindung + stehen daher nur zur Verfügung, wenn Ihr Smartpnone über eine Datenverbindung mit dem Internet verfügt, z.B. über UMTS, LTE oder WLAN, um hierüber auf die CWA-Dienste zugreifen zu können. Ohne Datenverbindung stehen einige oder alle Funktionen der App nicht zur Verfügung. Dies gilt auch, wenn Sie - Ihr Endgerät in den Flugmodus versetzen oder ausschalten. + Ihr Smartphone in den Flugmodus versetzen oder ausschalten. </p> <p> Das COVID-19-Benachrichtigungssystem muss aktiviert sein </p> <p> - Das COVID-19-Benachrichtigungssystem Ihres Endgeräts muss aktiviert und für + Das COVID-19-Benachrichtigungssystem Ihres Smartphones muss aktiviert und für die Region „Deutschland“ bzw. die App freigegeben sein. </p> <p> <strong><em>Hintergrundaktualisierung</em></strong> </p> <p> - Die App muss dauerhaft im Hintergrundbetrieb laufen. Wenn Sie das Endgerät + Die App muss dauerhaft im Hintergrundbetrieb laufen. Wenn Sie das Smartphone neu starten (z.B. nach dem Ausschalten, nachdem die Batterie leer war oder nach einem Update des Betriebssystems) oder nach einer erzwungenen Beendigung der App müssen Sie auch die App neu starten. </p> <p> - Einstellungen im Endgerät + Einstellungen im Smartphone </p> <p> Für die Nutzung der App müssen Sie ferner die Bluetooth (BLE)-Funktionen - auf Ihrem Endgerät aktivieren und ggf. zur Verwendung durch die App + auf Ihrem Smartphone aktivieren und ggf. zur Verwendung durch die App freigeben. </p> <p> Für die Nutzung der App empfiehlt das RKI ferner folgende Funktionen auf - Ihrem Endgerät zu aktivieren und ggf. zur Verwendung durch die App + Ihrem Smartphone zu aktivieren und ggf. zur Verwendung durch die App freizugeben, auch wenn diese nicht Voraussetzung für die Nutzung der grundlegenden Funktionen der App sind: </p> @@ -499,7 +499,7 @@ </li> </ul> <p> - Bitte prüfen Sie in den Einstellungen Ihres Endgeräts, ob diese Funktionen + Bitte prüfen Sie in den Einstellungen Ihres Smartphones, ob diese Funktionen aktiviert und für die Verwendung der App freigegeben sind. </p> <p> @@ -558,12 +558,12 @@ <li> Für die Bewertung des Infektionsrisikos nutzt die App die Aufzeichnungen zur Dämpfung des Bluetooth-Signals. Eine geringere - Dämpfung bedeutet dabei grundsätzlich, dass das andere Endgerät näher + Dämpfung bedeutet dabei grundsätzlich, dass das andere Smartphone näher ist. Eine höhere Dämpfung kann entweder bedeuten, dass das andere - Endgerät weiter entfernt ist (also eine Entfernung von mehr als zwei - Metern) oder dass sich zwischen den beiden Endgeräten etwas befindet, + Smartphone weiter entfernt ist (also eine Entfernung von mehr als zwei + Metern) oder dass sich zwischen den beiden Smartphones etwas befindet, was das Signal blockiert. Das können Objekte wie eine Wand oder eine - Tasche, in der sich das Endgerät befindet, sein, aber genauso Personen + Tasche, in der sich das Smartphone befindet, sein, aber genauso Personen oder Tiere. </li> </ul> @@ -629,7 +629,7 @@ Sie dürfen die App und die Schnittstellen zu den CWA-Diensten nicht missbräuchlich verwenden. Sie dürfen die CWA-Dienste nicht für andere Zwecke nutzen als den bestimmungsgemäßen Betrieb der App auf Ihrem - Endgerät. Sie dürfen auf die CWA-Dienste ausschließlich über die App + Smartphone. Sie dürfen auf die CWA-Dienste ausschließlich über die App zugreifen. </p> <p> @@ -670,7 +670,7 @@ <p> Das RKI behält sich vor, diese Nutzungsbedingungen zu ändern. Wenn Sie den geänderten Nutzungsbedingungen nicht zustimmen, können Sie die App und die - CWA-Dienste nicht mehr nutzen und müssen die App von Ihrem Endgerät + CWA-Dienste nicht mehr nutzen und müssen die App von Ihrem Smartphone löschen. </p> <p> @@ -713,9 +713,9 @@ </li> </ul> <p> - Für die Datensicherung Ihres Endgeräts sowie ggf. damit verbundener Systeme + Für die Datensicherung Ihres Smartphones sowie ggf. damit verbundener Systeme sind Sie verantwortlich, inklusive der Datensicherung sämtlicher anderer - Apps, welche auf Ihrem Endgerät gespeichert sind. + Apps, welche auf Ihrem Smartphone gespeichert sind. </p> <p> 12. BESONDERE BEDINGUNGEN FÃœR DIE IOS-VERSION DER APP diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt index aa3b394519b2dd83735ddccbc104115312b6a37c..7c70a16f944ea3fb4963af1a4d05eca980c6cf8b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloader.kt @@ -93,6 +93,7 @@ class KeyFileDownloader @Inject constructor( Timber.tag(TAG).w("Missing keyfile for : %s", keyInfo) null } else { + Timber.tag(TAG).v("Providing available key: %s", keyInfo) path } } @@ -172,11 +173,21 @@ class KeyFileDownloader @Inject constructor( availableCountries: List<LocationCode>, itemLimit: Int ): List<CountryHours> { - val availableHours = availableCountries.flatMap { location -> var remainingItems = itemLimit // Descending because we go backwards newest -> oldest - keyServer.getDayIndex(location).sortedDescending().mapNotNull { day -> + val indexWithToday = keyServer.getDayIndex(location).let { + val lastDayInIndex = it.maxOrNull() + Timber.tag(TAG).v("Last day in index: %s", lastDayInIndex) + if (lastDayInIndex != null) { + it.plus(lastDayInIndex.plusDays(1)) + } else { + it + } + } + Timber.tag(TAG).v("Day index with (fake) today entry: %s", indexWithToday) + + indexWithToday.sortedDescending().mapNotNull { day -> // Limit reached, return null (filtered out) instead of new CountryHours object if (remainingItems <= 0) return@mapNotNull null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServer.kt index 7114ff6348838fa96b53ff2d801d483348608d39..decf192f7e4e028a4e4845efcc6bd19ea0c09d31 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServer.kt @@ -110,6 +110,6 @@ class DiagnosisKeyServer @Inject constructor( companion object { private val TAG = DiagnosisKeyServer::class.java.simpleName private val DAY_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd") - private val HOUR_FORMATTER = DateTimeFormat.forPattern("HH") + private val HOUR_FORMATTER = DateTimeFormat.forPattern("H") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt index 7b9f389a549f5c356ba6a10e1a0ddfb77f530f82..be8e13c919927fcf9ea8da8c30100b8b8fadfd82 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/interoperability/InteroperabilityConfigurationFragment.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import androidx.fragment.app.Fragment +import de.rki.coronawarnapp.CoronaWarnApplication import de.rki.coronawarnapp.R import de.rki.coronawarnapp.databinding.FragmentInteroperabilityConfigurationBinding import de.rki.coronawarnapp.ui.main.MainActivity @@ -29,7 +30,6 @@ class InteroperabilityConfigurationFragment : private val networkCallback = object : ConnectivityHelper.NetworkCallback() { override fun onNetworkAvailable() { vm.getAllCountries() - unregisterNetworkCallback() } override fun onNetworkUnavailable() { @@ -44,6 +44,10 @@ class InteroperabilityConfigurationFragment : binding.countryData = it } + if (ConnectivityHelper.isNetworkEnabled(CoronaWarnApplication.getAppContext())) { + registerNetworkCallback() + } + vm.saveInteroperabilityUsed() binding.interoperabilityConfigurationHeader.headerButtonBack.buttonIcon.setOnClickListener { @@ -58,14 +62,13 @@ class InteroperabilityConfigurationFragment : binding.interoperabilityConfigurationCountryList .noCountriesRiskdetailsInfoview.riskDetailsOpenSettingsButton.setOnClickListener { - val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY) - } else { - Intent(Settings.ACTION_SETTINGS) + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY) + } else { + Intent(Settings.ACTION_SETTINGS) + } + startActivity(intent) } - startActivity(intent) - registerNetworkCallback() - } } private fun registerNetworkCallback() { @@ -88,4 +91,11 @@ class InteroperabilityConfigurationFragment : super.onDestroy() unregisterNetworkCallback() } + + override fun onResume() { + super.onResume() + if (ConnectivityHelper.isNetworkEnabled(CoronaWarnApplication.getAppContext())) { + registerNetworkCallback() + } + } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt index 3b4644059a48bd0042a9f64b5df109560d80f4cb..960dae2060523e92a70b98da9b9b1c7669e3df75 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/download/KeyFileDownloaderTest.kt @@ -5,6 +5,7 @@ import de.rki.coronawarnapp.diagnosiskeys.server.DiagnosisKeyServer import de.rki.coronawarnapp.diagnosiskeys.server.DownloadInfo import de.rki.coronawarnapp.diagnosiskeys.server.LocationCode import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo +import de.rki.coronawarnapp.diagnosiskeys.storage.CachedKeyInfo.Type import de.rki.coronawarnapp.diagnosiskeys.storage.KeyCacheRepository import de.rki.coronawarnapp.diagnosiskeys.storage.legacy.LegacyKeyCacheMigration import de.rki.coronawarnapp.storage.AppSettings @@ -48,7 +49,7 @@ class KeyFileDownloaderTest : BaseIOTest() { private lateinit var legacyMigration: LegacyKeyCacheMigration @MockK - private lateinit var diagnosisKeyServer: DiagnosisKeyServer + private lateinit var server: DiagnosisKeyServer @MockK private lateinit var deviceStorage: DeviceStorage @@ -59,6 +60,10 @@ class KeyFileDownloaderTest : BaseIOTest() { private val testDir = File(IO_TEST_BASEDIR, this::class.simpleName!!) private val keyRepoData = mutableMapOf<String, CachedKeyInfo>() + private val String.loc get() = LocationCode(this) + private val String.day get() = LocalDate.parse(this) + private val String.hour get() = LocalTime.parse(this) + @BeforeEach fun setup() { MockKAnnotations.init(this) @@ -67,44 +72,38 @@ class KeyFileDownloaderTest : BaseIOTest() { every { settings.isLast3HourModeEnabled } returns false - coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf( - LocationCode("DE"), - LocationCode("NL") - ) + coEvery { server.getCountryIndex() } returns listOf("DE".loc, "NL".loc) coEvery { deviceStorage.requireSpacePrivateStorage(any()) } returns mockk<DeviceStorage.CheckResult>().apply { every { isSpaceAvailable } returns true } - coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf( - LocationCode("DE"), LocationCode("NL") - ) - coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf( - LocalDate.parse("2020-09-01"), LocalDate.parse("2020-09-02") + coEvery { server.getDayIndex("DE".loc) } returns listOf( + "2020-09-01".day, "2020-09-02".day ) coEvery { - diagnosisKeyServer.getHourIndex(LocationCode("DE"), LocalDate.parse("2020-09-01")) - } returns listOf( - LocalTime.parse("18"), LocalTime.parse("19"), LocalTime.parse("20") - ) + server.getHourIndex("DE".loc, "2020-09-01".day) + } returns (0..23).map { "$it".hour } coEvery { - diagnosisKeyServer.getHourIndex(LocationCode("DE"), LocalDate.parse("2020-09-02")) - } returns listOf( - LocalTime.parse("20"), LocalTime.parse("21") - ) - coEvery { diagnosisKeyServer.getDayIndex(LocationCode("NL")) } returns listOf( - LocalDate.parse("2020-09-02"), LocalDate.parse("2020-09-03") - ) + server.getHourIndex("DE".loc, "2020-09-02".day) + } returns (0..23).map { "$it".hour } coEvery { - diagnosisKeyServer.getHourIndex(LocationCode("NL"), LocalDate.parse("2020-09-02")) - } returns listOf( - LocalTime.parse("20"), LocalTime.parse("21"), LocalTime.parse("22") + server.getHourIndex("DE".loc, "2020-09-03".day) + } returns (0..12).map { "$it".hour } + + coEvery { server.getDayIndex("NL".loc) } returns listOf( + "2020-09-01".day, "2020-09-02".day ) coEvery { - diagnosisKeyServer.getHourIndex(LocationCode("NL"), LocalDate.parse("2020-09-03")) - } returns listOf( - LocalTime.parse("22"), LocalTime.parse("23") - ) - coEvery { diagnosisKeyServer.downloadKeyFile(any(), any(), any(), any(), any()) } answers { + server.getHourIndex("NL".loc, "2020-09-01".day) + } returns (0..23).map { "$it".hour } + coEvery { + server.getHourIndex("NL".loc, "2020-09-02".day) + } returns (0..23).map { "$it".hour } + coEvery { + server.getHourIndex("NL".loc, "2020-09-03".day) + } returns (0..12).map { "$it".hour } + + coEvery { server.downloadKeyFile(any(), any(), any(), any(), any()) } answers { mockDownloadServerDownload( locationCode = arg(0), day = arg(1), @@ -120,7 +119,7 @@ class KeyFileDownloaderTest : BaseIOTest() { mockKeyCacheUpdateComplete(arg(0), arg(1)) } coEvery { keyCache.getEntriesForType(any()) } answers { - val type = arg<CachedKeyInfo.Type>(0) + val type = arg<Type>(0) keyRepoData.values.filter { it.type == type }.map { it to File(testDir, it.id) } } coEvery { keyCache.getAllCachedKeys() } answers { @@ -144,7 +143,7 @@ class KeyFileDownloaderTest : BaseIOTest() { } private fun mockKeyCacheCreateEntry( - type: CachedKeyInfo.Type, + type: Type, location: LocationCode, dayIdentifier: LocalDate, hourIdentifier: LocalTime? @@ -167,7 +166,8 @@ class KeyFileDownloaderTest : BaseIOTest() { checksum: String ) { keyRepoData[keyInfo.id] = keyInfo.copy( - isDownloadComplete = true, checksumMD5 = checksum + isDownloadComplete = true, + checksumMD5 = checksum ) } @@ -187,7 +187,7 @@ class KeyFileDownloaderTest : BaseIOTest() { } private fun mockAddData( - type: CachedKeyInfo.Type, + type: Type, location: LocationCode, day: LocalDate, hour: LocalTime?, @@ -209,7 +209,7 @@ class KeyFileDownloaderTest : BaseIOTest() { private fun createDownloader(): KeyFileDownloader { val downloader = KeyFileDownloader( deviceStorage = deviceStorage, - keyServer = diagnosisKeyServer, + keyServer = server, keyCache = keyCache, legacyKeyCache = legacyMigration, settings = settings, @@ -247,7 +247,7 @@ class KeyFileDownloaderTest : BaseIOTest() { runBlocking { shouldThrow<InsufficientStorageException> { - downloader.asyncFetchKeyFiles(listOf(LocationCode("DE"))) + downloader.asyncFetchKeyFiles(listOf("DE".loc)) } } } @@ -264,20 +264,20 @@ class KeyFileDownloaderTest : BaseIOTest() { runBlocking { shouldThrow<InsufficientStorageException> { - downloader.asyncFetchKeyFiles(listOf(LocationCode("DE"))) + downloader.asyncFetchKeyFiles(listOf("DE".loc)) } } } @Test fun `error during country index fetch`() { - coEvery { diagnosisKeyServer.getCountryIndex() } throws IOException() + coEvery { server.getCountryIndex() } throws IOException() val downloader = createDownloader() runBlocking { shouldThrow<IOException> { - downloader.asyncFetchKeyFiles(listOf(LocationCode("DE"))) + downloader.asyncFetchKeyFiles(listOf("DE".loc)) } } } @@ -287,34 +287,32 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 4 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 4 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-02".day, hourIdentifier = null ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + dayIdentifier = "2020-09-02".day, hourIdentifier = null ) } @@ -326,17 +324,16 @@ class KeyFileDownloaderTest : BaseIOTest() { @Test fun `day fetch with existing data`() { mockAddData( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - day = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + day = "2020-09-01".day, hour = null, isCompleted = true ) - mockAddData( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + day = "2020-09-02".day, hour = null, isCompleted = true ) @@ -344,22 +341,20 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 4 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 4 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-02".day, hourIdentifier = null ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) } @@ -372,21 +367,19 @@ class KeyFileDownloaderTest : BaseIOTest() { @Test fun `day fetch deletes stale data`() { - coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf( - LocalDate.parse("2020-09-02") - ) + coEvery { server.getDayIndex("DE".loc) } returns listOf("2020-09-02".day) val (staleKeyInfo, _) = mockAddData( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - day = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + day = "2020-09-01".day, hour = null, isCompleted = true ) mockAddData( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + day = "2020-09-02".day, hour = null, isCompleted = true ) @@ -394,22 +387,20 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 3 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 3 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-02".day, hourIdentifier = null ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), + type = Type.COUNTRY_DAY, + location = "NL".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) } @@ -421,7 +412,7 @@ class KeyFileDownloaderTest : BaseIOTest() { @Test fun `day fetch skips single download failures`() { var dlCounter = 0 - coEvery { diagnosisKeyServer.downloadKeyFile(any(), any(), any(), any(), any()) } answers { + coEvery { server.downloadKeyFile(any(), any(), any(), any(), any()) } answers { dlCounter++ if (dlCounter == 2) throw IOException("Timeout") mockDownloadServerDownload( @@ -435,9 +426,7 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 3 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 3 } // We delete the entry for the failed download @@ -451,48 +440,46 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 6 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 6 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("21") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("20") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "11".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-01"), - hourIdentifier = LocalTime.parse("20") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "10".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("23") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("22") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "11".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("22") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "10".hour ) } coVerify(exactly = 6) { keyCache.markKeyComplete(any(), any()) } @@ -508,17 +495,17 @@ class KeyFileDownloaderTest : BaseIOTest() { every { settings.isLast3HourModeEnabled } returns true mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - day = LocalDate.parse("2020-09-01"), - hour = LocalTime.parse("20"), + type = Type.COUNTRY_HOUR, + location = "DE".loc, + day = "2020-09-03".day, + hour = "11".hour, isCompleted = true ) mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-02"), - hour = LocalTime.parse("22"), + type = Type.COUNTRY_HOUR, + location = "NL".loc, + day = "2020-09-03".day, + hour = "11".hour, isCompleted = true ) @@ -526,45 +513,39 @@ class KeyFileDownloaderTest : BaseIOTest() { runBlocking { downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) + listOf("DE".loc, "NL".loc) ).size shouldBe 6 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("21") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("20") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "10".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("23") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("22") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "10".hour ) } coVerify(exactly = 4) { - diagnosisKeyServer.downloadKeyFile( - any(), - any(), - any(), - any(), - any() - ) + server.downloadKeyFile(any(), any(), any(), any(), any()) } coVerify { deviceStorage.requireSpacePrivateStorage(90112L) } } @@ -574,79 +555,71 @@ class KeyFileDownloaderTest : BaseIOTest() { every { settings.isLast3HourModeEnabled } returns true val (staleKey1, _) = mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-02"), - hour = LocalTime.parse("12"), // Stale hour + type = Type.COUNTRY_HOUR, + location = "DE".loc, + day = "2020-09-02".day, + hour = "01".hour, // Stale hour isCompleted = true ) val (staleKey2, _) = mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-01"), // Stale day - hour = LocalTime.parse("22"), + type = Type.COUNTRY_HOUR, + location = "NL".loc, + day = "2020-09-02".day, // Stale day + hour = "01".hour, isCompleted = true ) mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - day = LocalDate.parse("2020-09-01"), - hour = LocalTime.parse("20"), + type = Type.COUNTRY_HOUR, + location = "DE".loc, + day = "2020-09-03".day, + hour = "10".hour, isCompleted = true ) mockAddData( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - day = LocalDate.parse("2020-09-02"), - hour = LocalTime.parse("22"), + type = Type.COUNTRY_HOUR, + location = "NL".loc, + day = "2020-09-03".day, + hour = "10".hour, isCompleted = true ) val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 6 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 6 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("21") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-02"), - hourIdentifier = LocalTime.parse("20") + type = Type.COUNTRY_HOUR, + location = "DE".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "11".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("23") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "12".hour ) keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_HOUR, - location = LocationCode("NL"), - dayIdentifier = LocalDate.parse("2020-09-03"), - hourIdentifier = LocalTime.parse("22") + type = Type.COUNTRY_HOUR, + location = "NL".loc, + dayIdentifier = "2020-09-03".day, + hourIdentifier = "11".hour ) } coVerify(exactly = 4) { - diagnosisKeyServer.downloadKeyFile( - any(), - any(), - any(), - any(), - any() - ) + server.downloadKeyFile(any(), any(), any(), any(), any()) } coVerify(exactly = 1) { keyCache.delete(listOf(staleKey1, staleKey2)) } } @@ -656,7 +629,7 @@ class KeyFileDownloaderTest : BaseIOTest() { every { settings.isLast3HourModeEnabled } returns true var dlCounter = 0 - coEvery { diagnosisKeyServer.downloadKeyFile(any(), any(), any(), any(), any()) } answers { + coEvery { server.downloadKeyFile(any(), any(), any(), any(), any()) } answers { dlCounter++ if (dlCounter == 2) throw IOException("Timeout") mockDownloadServerDownload( @@ -670,9 +643,7 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 5 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 5 } // We delete the entry for the failed download @@ -682,9 +653,9 @@ class KeyFileDownloaderTest : BaseIOTest() { @Test fun `not completed cache entries are overwritten`() { mockAddData( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - day = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + day = "2020-09-01".day, hour = null, isCompleted = false ) @@ -692,16 +663,14 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 4 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 4 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) } @@ -719,42 +688,30 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE"), LocationCode("NL")) - ).size shouldBe 3 + downloader.asyncFetchKeyFiles(listOf("DE".loc, "NL".loc)).size shouldBe 3 } coVerify(exactly = 4) { - diagnosisKeyServer.downloadKeyFile( - any(), - any(), - any(), - any(), - any() - ) + server.downloadKeyFile(any(), any(), any(), any(), any()) } } @Test fun `store server md5`() { - coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(LocationCode("DE")) - coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf( - LocalDate.parse("2020-09-01") - ) + coEvery { server.getCountryIndex() } returns listOf("DE".loc) + coEvery { server.getDayIndex("DE".loc) } returns listOf("2020-09-01".day) val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE")) - ).size shouldBe 1 + downloader.asyncFetchKeyFiles(listOf("DE".loc)).size shouldBe 1 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) } @@ -767,11 +724,9 @@ class KeyFileDownloaderTest : BaseIOTest() { @Test fun `use local MD5 as fallback if there is none available from the server`() { - coEvery { diagnosisKeyServer.getCountryIndex() } returns listOf(LocationCode("DE")) - coEvery { diagnosisKeyServer.getDayIndex(LocationCode("DE")) } returns listOf( - LocalDate.parse("2020-09-01") - ) - coEvery { diagnosisKeyServer.downloadKeyFile(any(), any(), any(), any(), any()) } answers { + coEvery { server.getCountryIndex() } returns listOf("DE".loc) + coEvery { server.getDayIndex("DE".loc) } returns listOf("2020-09-01".day) + coEvery { server.downloadKeyFile(any(), any(), any(), any(), any()) } answers { mockDownloadServerDownload( locationCode = arg(0), day = arg(1), @@ -784,16 +739,14 @@ class KeyFileDownloaderTest : BaseIOTest() { val downloader = createDownloader() runBlocking { - downloader.asyncFetchKeyFiles( - listOf(LocationCode("DE")) - ).size shouldBe 1 + downloader.asyncFetchKeyFiles(listOf("DE".loc)).size shouldBe 1 } coVerify { keyCache.createCacheEntry( - type = CachedKeyInfo.Type.COUNTRY_DAY, - location = LocationCode("DE"), - dayIdentifier = LocalDate.parse("2020-09-01"), + type = Type.COUNTRY_DAY, + location = "DE".loc, + dayIdentifier = "2020-09-01".day, hourIdentifier = null ) } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServerTest.kt index bce772243e8ac7803287c082889e2d06c06c527c..08aed5e5ad0fcfd395d52169b7c3b3d760e9b049 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/diagnosiskeys/server/DiagnosisKeyServerTest.kt @@ -81,7 +81,7 @@ class DiagnosisKeyServerTest : BaseIOTest() { fun `download hour index for country and day`() { val downloadServer = createDownloadServer() coEvery { api.getHourIndex("DE", "2000-01-01") } returns listOf( - "20", "21" + "1", "2", "20", "21" ) runBlocking { @@ -89,7 +89,7 @@ class DiagnosisKeyServerTest : BaseIOTest() { LocationCode("DE"), LocalDate.parse("2000-01-01") ) shouldBe listOf( - "20:00", "21:00" + "01:00", "02:00", "20:00", "21:00" ).map { LocalTime.parse(it) } } @@ -122,28 +122,51 @@ class DiagnosisKeyServerTest : BaseIOTest() { } @Test - fun `download key files for hour`() { + fun `download key files for hour and check hour format`() { val downloadServer = createDownloadServer() - coEvery { - api.downloadKeyFileForHour( - "DE", - "2000-01-01", - "01" - ) - } returns Response.success("testdata-hour".toResponseBody()) - - val targetFile = File(testDir, "hour-keys") runBlocking { + coEvery { + api.downloadKeyFileForHour( + "DE", + "2000-01-01", + "1" // no leading ZEROS! + ) + } returns Response.success("testdata-hour".toResponseBody()) + + val targetFile = File(testDir, "hour-keys") + downloadServer.downloadKeyFile( locationCode = LocationCode("DE"), day = LocalDate.parse("2000-01-01"), hour = LocalTime.parse("01:00"), saveTo = targetFile ) + + targetFile.exists() shouldBe true + targetFile.readText() shouldBe "testdata-hour" } - targetFile.exists() shouldBe true - targetFile.readText() shouldBe "testdata-hour" + runBlocking { + coEvery { + api.downloadKeyFileForHour( + "DE", + "2000-01-01", + "13" + ) + } returns Response.success("testdata-hour".toResponseBody()) + + val targetFile = File(testDir, "hour-keys") + + downloadServer.downloadKeyFile( + locationCode = LocationCode("DE"), + day = LocalDate.parse("2000-01-01"), + hour = LocalTime.parse("13:00"), + saveTo = targetFile + ) + + targetFile.exists() shouldBe true + targetFile.readText() shouldBe "testdata-hour" + } } }