diff --git a/Corona-Warn-App/build.gradle b/Corona-Warn-App/build.gradle index 4861612811d..52c46716a9f 100644 --- a/Corona-Warn-App/build.gradle +++ b/Corona-Warn-App/build.gradle @@ -403,6 +403,7 @@ dependencies { implementation("org.boofcv:boofcv-android:$boofCV_version") { exclude group: 'com.google.protobuf' } + constraints { implementation('net.lingala.zip4j:zip4j:2.10.0') { because 'fixing zip4j vulnerability' @@ -487,7 +488,11 @@ dependencies { implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.google.guava:guava:30.0-android' implementation "joda-time:joda-time:2.10.13" - implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:2.13.2" + + // Jackson + def jackson = "2.13.2" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-joda:$jackson" + implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson" implementation 'com.networknt:json-schema-validator:1.0.69' implementation 'net.swiftzer.semver:semver:1.2.0' diff --git a/Corona-Warn-App/proguard-rules.pro b/Corona-Warn-App/proguard-rules.pro index 68a5785b8fa..52257c3f2e7 100644 --- a/Corona-Warn-App/proguard-rules.pro +++ b/Corona-Warn-App/proguard-rules.pro @@ -117,4 +117,5 @@ -keep class de.rki.coronawarnapp.ccl.dccwalletinfo.model.** { *; } -keep class de.rki.coronawarnapp.ccl.dccadmission.model.** { *; } -keep class de.rki.coronawarnapp.covidcertificate.person.model.** { *; } --keep class de.rki.coronawarnapp.covidcertificate.common.certificate.** { *; } \ No newline at end of file +-keep class de.rki.coronawarnapp.covidcertificate.common.certificate.** { *; } +-keep class de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.** { *; } \ No newline at end of file diff --git a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/bugreporting/DebugLogFragmentTest.kt b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/bugreporting/DebugLogFragmentTest.kt index 90b342355c0..c863c5ce67d 100644 --- a/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/bugreporting/DebugLogFragmentTest.kt +++ b/Corona-Warn-App/src/androidTest/java/de/rki/coronawarnapp/bugreporting/DebugLogFragmentTest.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter import de.rki.coronawarnapp.bugreporting.debuglog.ui.DebugLogFragment import de.rki.coronawarnapp.bugreporting.debuglog.ui.DebugLogViewModel +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage import de.rki.coronawarnapp.nearby.ENFClient import io.mockk.MockKAnnotations import io.mockk.every @@ -29,7 +30,7 @@ class DebugLogFragmentTest : BaseUITest() { @MockK lateinit var debugLogger: DebugLogger @MockK lateinit var enfClient: ENFClient - @MockK lateinit var bugReportingSettings: BugReportingSettings + @MockK lateinit var uploadHistoryStorage: UploadHistoryStorage @MockK lateinit var logSnapshotter: LogSnapshotter private lateinit var inactiveViewModel: DebugLogViewModel @@ -85,11 +86,11 @@ class DebugLogFragmentTest : BaseUITest() { ): DebugLogViewModel { val vm = spyk( DebugLogViewModel( - debugLogger, - TestDispatcherProvider(), - enfClient, - bugReportingSettings, - logSnapshotter + debugLogger = debugLogger, + dispatcherProvider = TestDispatcherProvider(), + enfClient = enfClient, + uploadHistoryStorage = uploadHistoryStorage, + logSnapshotter = logSnapshotter ) ) with(vm) { diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/datadonation/analytics/storage/DefaultLastAnalyticsSubmissionLogger.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/datadonation/analytics/storage/DefaultLastAnalyticsSubmissionLogger.kt index 560126c5d9d..0052050b42a 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/datadonation/analytics/storage/DefaultLastAnalyticsSubmissionLogger.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/datadonation/analytics/storage/DefaultLastAnalyticsSubmissionLogger.kt @@ -12,13 +12,13 @@ import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.serialization.BaseGson -import de.rki.coronawarnapp.util.serialization.adapter.InstantAdapter +import de.rki.coronawarnapp.util.serialization.adapter.JavaInstantAdapter import de.rki.coronawarnapp.util.serialization.fromJson import de.rki.coronawarnapp.util.serialization.toJson import kotlinx.coroutines.withContext import okio.ByteString.Companion.decodeBase64 import okio.ByteString.Companion.toByteString -import org.joda.time.Instant +import java.time.Instant import timber.log.Timber import java.io.File import javax.inject.Inject @@ -34,7 +34,7 @@ class DefaultLastAnalyticsSubmissionLogger @Inject constructor( private val gson by lazy { baseGson.newBuilder() - .registerTypeAdapter(Instant::class.java, InstantAdapter()) + .registerTypeAdapter(Instant::class.java, JavaInstantAdapter()) .registerTypeAdapter(PpaData.PPADataAndroid::class.java, PPADataAndroidAdapter()) .create() } @@ -46,7 +46,7 @@ class DefaultLastAnalyticsSubmissionLogger @Inject constructor( } val dataObject = LastAnalyticsSubmission( - timestamp = timeStamper.nowUTC, + timestamp = timeStamper.nowJavaUTC, ppaDataAndroid = analyticsProto ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSettings.kt deleted file mode 100644 index 8ab773425f1..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSettings.kt +++ /dev/null @@ -1,36 +0,0 @@ -package de.rki.coronawarnapp.bugreporting - -import android.content.Context -import com.google.gson.Gson -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.UploadHistory -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.reset.Resettable -import de.rki.coronawarnapp.util.serialization.BaseGson -import timber.log.Timber -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class BugReportingSettings @Inject constructor( - @AppContext private val context: Context, - @BaseGson private val gson: Gson -) : Resettable { - - private val prefs by lazy { - context.getSharedPreferences("bugreporting_localdata", Context.MODE_PRIVATE) - } - - val uploadHistory: FlowPreference = prefs.createFlowPreference( - key = "upload.history", - reader = FlowPreference.gsonReader(gson, UploadHistory()), - writer = FlowPreference.gsonWriter(gson) - ) - - override suspend fun reset() { - Timber.d("reset()") - prefs.clearAndNotify() - } -} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt index 5b5f3afdf65..30197340358 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/BugReportingSharedModule.kt @@ -26,6 +26,7 @@ import de.rki.coronawarnapp.bugreporting.censors.submission.RACoronaTestCensor import de.rki.coronawarnapp.bugreporting.censors.submission.RapidQrCodeCensor import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebugLoggerScope import de.rki.coronawarnapp.bugreporting.debuglog.internal.DebuggerScope +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorageModule import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.LogUploadApiV1 import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadAuthApiV1 import de.rki.coronawarnapp.environment.bugreporting.LogUploadHttpClient @@ -33,7 +34,6 @@ import de.rki.coronawarnapp.environment.bugreporting.LogUploadServerUrl import de.rki.coronawarnapp.environment.datadonation.DataDonationCDNHttpClient import de.rki.coronawarnapp.environment.datadonation.DataDonationCDNServerUrl import de.rki.coronawarnapp.util.CWADebug -import de.rki.coronawarnapp.util.reset.Resettable import kotlinx.coroutines.CoroutineScope import okhttp3.OkHttpClient import retrofit2.Retrofit @@ -42,7 +42,12 @@ import retrofit2.converter.protobuf.ProtoConverterFactory import javax.inject.Singleton @Suppress("TooManyFunctions") -@Module(includes = [BugReportingSharedModule.BindsModule::class, BugReportingSharedModule.ResetModule::class]) +@Module( + includes = [ + BugReportingSharedModule.BindsModule::class, + UploadHistoryStorageModule::class + ] +) object BugReportingSharedModule { @Reusable @@ -84,14 +89,6 @@ object BugReportingSharedModule { @Provides fun scope(): CoroutineScope = DebugLoggerScope - @Module - internal interface ResetModule { - - @Binds - @IntoSet - fun bindResettableBugReportingSettings(resettable: BugReportingSettings): Resettable - } - @Module internal interface BindsModule { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt index 8e83edfe1e1..1298db2a3c7 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/DebugLogViewModel.kt @@ -4,9 +4,9 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.bugreporting.BugReportingSettings import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage import de.rki.coronawarnapp.nearby.ENFClient import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.coroutine.DispatcherProvider @@ -22,13 +22,13 @@ class DebugLogViewModel @AssistedInject constructor( private val debugLogger: DebugLogger, dispatcherProvider: DispatcherProvider, private val enfClient: ENFClient, - bugReportingSettings: BugReportingSettings, + uploadHistoryStorage: UploadHistoryStorage, private val logSnapshotter: LogSnapshotter ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { private val isActionInProgress = MutableStateFlow(false) - val logUploads = bugReportingSettings.uploadHistory.flow + val logUploads = uploadHistoryStorage.uploadHistory .asLiveData(context = dispatcherProvider.Default) val state: LiveData = combine( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/HistoryItemAdapter.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/HistoryItemAdapter.kt index 16e29cb72e9..9da16a2a871 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/HistoryItemAdapter.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/HistoryItemAdapter.kt @@ -5,7 +5,7 @@ import android.content.ClipboardManager import android.content.Context import android.view.ViewGroup import de.rki.coronawarnapp.R -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload import de.rki.coronawarnapp.databinding.BugreportingUploadHistoryItemBinding import de.rki.coronawarnapp.ui.lists.BaseAdapter import de.rki.coronawarnapp.util.lists.BindableVH diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/LogUploadHistoryViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/LogUploadHistoryViewModel.kt index 93d6191cb3d..8a95f921d7e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/LogUploadHistoryViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/ui/upload/history/LogUploadHistoryViewModel.kt @@ -4,8 +4,8 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import de.rki.coronawarnapp.bugreporting.BugReportingSettings -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory @@ -13,10 +13,10 @@ import kotlinx.coroutines.flow.map class LogUploadHistoryViewModel @AssistedInject constructor( dispatcherProvider: DispatcherProvider, - bugReportingSettings: BugReportingSettings + uploadHistoryStorage: UploadHistoryStorage ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val logUploads: LiveData> = bugReportingSettings.uploadHistory.flow + val logUploads: LiveData> = uploadHistoryStorage.uploadHistory .map { history -> history.logs.sortedByDescending { it.uploadedAt } } .asLiveData(context = dispatcherProvider.Default) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploader.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploader.kt index b2c1dac5df5..d2430de7719 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploader.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploader.kt @@ -1,8 +1,8 @@ package de.rki.coronawarnapp.bugreporting.debuglog.upload -import de.rki.coronawarnapp.bugreporting.BugReportingSettings import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.LogUploadServer import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadAuthorizer import timber.log.Timber @@ -14,7 +14,7 @@ class SnapshotUploader @Inject constructor( private val snapshotter: LogSnapshotter, private val uploadServer: LogUploadServer, private val authorizer: LogUploadAuthorizer, - private val bugReportingSettings: BugReportingSettings + private val uploadHistoryStorage: UploadHistoryStorage ) { suspend fun uploadSnapshot(): LogUpload { @@ -38,7 +38,7 @@ class SnapshotUploader @Inject constructor( } } - bugReportingSettings.uploadHistory.update { oldHistory -> + uploadHistoryStorage.update { oldHistory -> val newLogs = oldHistory.logs.toMutableList() if (newLogs.size >= 10) { newLogs.removeFirst().also { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/LogUpload.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/LogUpload.kt deleted file mode 100644 index 7e8afb534ed..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/LogUpload.kt +++ /dev/null @@ -1,8 +0,0 @@ -package de.rki.coronawarnapp.bugreporting.debuglog.upload.history - -import java.time.Instant - -data class LogUpload( - val id: String, - val uploadedAt: Instant -) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/UploadHistory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/UploadHistory.kt deleted file mode 100644 index fa8dc33641b..00000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/UploadHistory.kt +++ /dev/null @@ -1,5 +0,0 @@ -package de.rki.coronawarnapp.bugreporting.debuglog.upload.history - -data class UploadHistory( - val logs: List = emptyList() -) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/LogUpload.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/LogUpload.kt new file mode 100644 index 00000000000..30092c3abef --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/LogUpload.kt @@ -0,0 +1,9 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model + +import com.fasterxml.jackson.annotation.JsonProperty +import java.time.Instant + +data class LogUpload( + @JsonProperty("id") val id: String, + @JsonProperty("uploadedAt") val uploadedAt: Instant +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/UploadHistory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/UploadHistory.kt new file mode 100644 index 00000000000..34c9cb47ee3 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/model/UploadHistory.kt @@ -0,0 +1,7 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model + +import com.fasterxml.jackson.annotation.JsonProperty + +data class UploadHistory( + @JsonProperty("logs") val logs: List = emptyList() +) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializer.kt new file mode 100644 index 00000000000..70e8bcbf402 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializer.kt @@ -0,0 +1,15 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import com.fasterxml.jackson.databind.ObjectMapper +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.util.datastore.BaseJsonSerializer +import de.rki.coronawarnapp.util.serialization.BaseJackson +import javax.inject.Inject + +class UploadHistorySerializer @Inject constructor( + @BaseJackson objectMapper: ObjectMapper +) : BaseJsonSerializer(objectMapper) { + + override val defaultValue: UploadHistory + get() = UploadHistory() +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorage.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorage.kt new file mode 100644 index 00000000000..e6e8666af9f --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorage.kt @@ -0,0 +1,32 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import androidx.datastore.core.DataStore +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.tag +import de.rki.coronawarnapp.util.reset.Resettable +import kotlinx.coroutines.flow.catch +import timber.log.Timber +import javax.inject.Inject + +@Reusable +class UploadHistoryStorage @Inject constructor( + private val dataStore: DataStore +) : Resettable { + + val uploadHistory = dataStore.data.catch { + Timber.tag(TAG).e("Failed to get UploadHistory") + emit(UploadHistory()) + } + + suspend fun update(transform: suspend (t: UploadHistory) -> UploadHistory) { + dataStore.updateData(transform) + } + + override suspend fun reset() { + Timber.tag(TAG).d("reset") + dataStore.updateData { UploadHistory() } + } +} + +private val TAG = tag() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModule.kt new file mode 100644 index 00000000000..5ae11356091 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModule.kt @@ -0,0 +1,75 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.datastore.core.DataStore +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.dataStoreFile +import androidx.datastore.migrations.SharedPreferencesMigration +import com.google.gson.Gson +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.multibindings.IntoSet +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.util.coroutine.AppScope +import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.di.AppContext +import de.rki.coronawarnapp.util.reset.Resettable +import de.rki.coronawarnapp.util.serialization.BaseGson +import de.rki.coronawarnapp.util.serialization.fromJson +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.plus +import timber.log.Timber +import javax.inject.Singleton + +@Module(includes = [UploadHistoryStorageModule.ResetModule::class]) +object UploadHistoryStorageModule { + + @Singleton + @Provides + fun provideDataStore( + serializer: UploadHistorySerializer, + @AppScope appScope: CoroutineScope, + dispatcherProvider: DispatcherProvider, + @AppContext context: Context, + migration: SharedPreferencesMigration + ): DataStore = DataStoreFactory.create( + serializer = serializer, + scope = appScope + dispatcherProvider.IO, + migrations = listOf(migration) + ) { + context.dataStoreFile(UPLOAD_HISTORY_DATA_STORE) + } + + @Provides + fun provideMigration( + @AppContext context: Context, + @BaseGson gson: Gson + ) = SharedPreferencesMigration( + context = context, + sharedPreferencesName = LEGACY_SHARED_PREFS + ) { sharedPreferencesView, uploadHistory -> + val migratedUploadHistory = runCatching { + // Data was converted with Gson before so use Gson to restore data to avoid corrupted data + // Gson and Jackson store Instants differently + sharedPreferencesView.getString(LEGACY_UPLOAD_HISTORY_KEY)?.let { gson.fromJson(it) } + } + .onFailure { Timber.tag("SharedPreferencesMigration").e(it, "Migration failed") } + .getOrNull() + + migratedUploadHistory ?: uploadHistory + } + + @Module + internal interface ResetModule { + + @Binds + @IntoSet + fun bindResettableUploadHistoryStorage(resettable: UploadHistoryStorage): Resettable + } +} + +private const val UPLOAD_HISTORY_DATA_STORE: String = "upload_history_data_store" +private const val LEGACY_SHARED_PREFS = "bugreporting_localdata" +@VisibleForTesting const val LEGACY_UPLOAD_HISTORY_KEY = "upload.history" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServer.kt index f94e398c624..c3fe6a34f47 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServer.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServer.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog.upload.server import dagger.Lazy import dagger.Reusable import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadOtp import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.files.determineMimeType diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt index f99851e7b28..b9f8d058921 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OTPAuthorizationResult.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation import androidx.annotation.Keep import com.google.gson.annotations.SerializedName -import org.joda.time.Instant +import java.time.Instant import java.util.UUID @Keep diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt index 1692cb7d4a6..3cd0561430d 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/OneTimePassword.kt @@ -3,7 +3,7 @@ package de.rki.coronawarnapp.datadonation import androidx.annotation.Keep import com.google.gson.annotations.SerializedName import de.rki.coronawarnapp.server.protocols.internal.ppdd.EdusOtp -import org.joda.time.Instant +import java.time.Instant import java.util.UUID @Keep diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt index 069629a5efa..f834e32b3cf 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/Analytics.kt @@ -23,8 +23,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeout -import org.joda.time.Hours import timber.log.Timber +import java.time.Duration import javax.inject.Inject import javax.inject.Singleton import kotlin.random.Random @@ -142,7 +142,7 @@ class Analytics @Inject constructor( if (result.successful) { settings.lastSubmittedTimestamp.update { - timeStamper.nowUTC + timeStamper.nowJavaUTC } logger.storeAnalyticsData(analyticsProto) @@ -171,14 +171,14 @@ class Analytics @Inject constructor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun stopDueToLastSubmittedTimestamp(): Boolean { val lastSubmit = settings.lastSubmittedTimestamp.value ?: return false - return lastSubmit.plus(Hours.hours(LAST_SUBMISSION_MIN_AGE_HOURS).toStandardDuration()) - .isAfter(timeStamper.nowUTC) + return lastSubmit.plus(Duration.ofHours(LAST_SUBMISSION_MIN_AGE_HOURS)) + .isAfter(timeStamper.nowJavaUTC) } @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun stopDueToTimeSinceOnboarding(): Boolean { val onboarding = onboardingSettings.onboardingCompletedTimestamp.value ?: return true - return onboarding.plus(Hours.hours(ONBOARDING_DELAY_HOURS).toStandardDuration()).isAfter(timeStamper.nowUTC) + return onboarding.plus(Duration.ofHours(ONBOARDING_DELAY_HOURS)).isAfter(timeStamper.nowJavaUTC) } suspend fun submitIfWanted(): Result = submissionLockoutMutex.withLock { @@ -244,8 +244,8 @@ class Analytics @Inject constructor( companion object { private val TAG = Analytics::class.java.simpleName - private const val LAST_SUBMISSION_MIN_AGE_HOURS = 23 - private const val ONBOARDING_DELAY_HOURS = 24 + private const val LAST_SUBMISSION_MIN_AGE_HOURS = 23L + private const val ONBOARDING_DELAY_HOURS = 24L data class PPADeviceAttestationRequest( val ppaData: PpaData.PPADataAndroid diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt index 7dbb579b345..2580487cd22 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/common/Calculations.kt @@ -3,9 +3,10 @@ package de.rki.coronawarnapp.datadonation.analytics.common import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState -import org.joda.time.Days -import org.joda.time.Instant -import org.joda.time.LocalDate +import de.rki.coronawarnapp.util.toJavaInstant +import java.time.Instant +import java.time.LocalDate +import java.time.temporal.ChronoUnit fun calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( lastDateAtRiskLevel: LocalDate?, @@ -14,22 +15,19 @@ fun calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( testRegisteredAt ?: return -1 lastDateAtRiskLevel ?: return -1 if (lastDateAtRiskLevel.isAfter(testRegisteredAt)) return -1 - return Days.daysBetween( - lastDateAtRiskLevel, - testRegisteredAt - ).days + return ChronoUnit.DAYS.between(lastDateAtRiskLevel, testRegisteredAt).toInt() } fun List.getLastChangeToHighPtRiskBefore(testRegisteredAt: Instant): Instant? { val successfulResults = filter { it.wasSuccessfullyCalculated } - .filter { it.calculatedAt <= testRegisteredAt } + .filter { it.calculatedAt.toJavaInstant() <= testRegisteredAt } .sortedByDescending { it.calculatedAt } successfulResults.forEachIndexed { index, ptRiskLevelResult -> if (ptRiskLevelResult.riskState == RiskState.INCREASED_RISK && (index == successfulResults.lastIndex || successfulResults[index + 1].riskState == RiskState.LOW_RISK) ) { - return ptRiskLevelResult.calculatedAt + return ptRiskLevelResult.calculatedAt.toJavaInstant() } } return null @@ -37,14 +35,14 @@ fun List.getLastChangeToHighPtRiskBefore(testRegisteredAt: In fun List.getLastChangeToHighEwRiskBefore(testRegisteredAt: Instant): Instant? { val successfulResults = filter { it.wasSuccessfullyCalculated } - .filter { it.calculatedAt <= testRegisteredAt } + .filter { it.calculatedAt.toJavaInstant() <= testRegisteredAt } .sortedByDescending { it.calculatedAt } successfulResults.forEachIndexed { index, ptRiskLevelResult -> if (ptRiskLevelResult.riskState == RiskState.INCREASED_RISK && (index == successfulResults.lastIndex || successfulResults[index + 1].riskState == RiskState.LOW_RISK) ) { - return ptRiskLevelResult.calculatedAt + return ptRiskLevelResult.calculatedAt.toJavaInstant() } } return null diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowRepository.kt index 50305e58de1..a774f49dc96 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowRepository.kt @@ -2,7 +2,7 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows import androidx.annotation.VisibleForTesting import de.rki.coronawarnapp.util.TimeStamper -import org.joda.time.Days +import java.time.Duration import javax.inject.Inject import javax.inject.Singleton @@ -45,7 +45,7 @@ class AnalyticsExposureWindowRepository @Inject constructor( } suspend fun deleteStaleData() { - val timestamp = timeStamper.nowUTC.minus(Days.days(15).toStandardDuration()).millis + val timestamp = timeStamper.nowJavaUTC.minus(Duration.ofDays(15)).toEpochMilli() dao.deleteReportedOlderThan(timestamp) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt index 8f59aff18bc..c25c4fe5f68 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollector.kt @@ -10,10 +10,12 @@ import de.rki.coronawarnapp.datadonation.analytics.storage.AnalyticsSettings import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.toJavaInstant +import de.rki.coronawarnapp.util.toJavaTime +import de.rki.coronawarnapp.util.toLocalDateUtc import kotlinx.coroutines.flow.first -import org.joda.time.Duration +import java.time.Duration import javax.inject.Inject class AnalyticsKeySubmissionCollector @Inject constructor( @@ -32,14 +34,14 @@ class AnalyticsKeySubmissionCollector @Inject constructor( if (disabled) return // do not overwrite once set if (type.storage.testResultReceivedAt.value > 0) return - type.storage.testResultReceivedAt.update { timeStamper.nowUTC.millis } + type.storage.testResultReceivedAt.update { timeStamper.nowJavaUTC.toEpochMilli() } } suspend fun reportTestRegistered(type: BaseCoronaTest.Type) { if (disabled) return - val testRegisteredAt = timeStamper.nowUTC - type.storage.testRegisteredAt.update { testRegisteredAt.millis } + val testRegisteredAt = timeStamper.nowJavaUTC + type.storage.testRegisteredAt.update { testRegisteredAt.toEpochMilli() } val lastResult = riskLevelStorage .latestAndLastSuccessfulCombinedEwPtRiskLevelResult @@ -51,10 +53,10 @@ class AnalyticsKeySubmissionCollector @Inject constructor( .first() .getLastChangeToHighEwRiskBefore(testRegisteredAt) ?.let { - val hours = Duration( + val hours = Duration.between( it, testRegisteredAt - ).standardHours.toInt() + ).toHours().toInt() type.storage.ewHoursSinceHighRiskWarningAtTestRegistration.update { hours } @@ -66,10 +68,10 @@ class AnalyticsKeySubmissionCollector @Inject constructor( .first() .getLastChangeToHighPtRiskBefore(testRegisteredAt) ?.let { - val hours = Duration( + val hours = Duration.between( it, testRegisteredAt - ).standardHours.toInt() + ).toHours().toInt() type.storage.ptHoursSinceHighRiskWarningAtTestRegistration.update { hours } @@ -78,14 +80,14 @@ class AnalyticsKeySubmissionCollector @Inject constructor( type.storage.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toLocalDateUtc(), + lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toJavaInstant()?.toLocalDateUtc(), testRegisteredAt.toLocalDateUtc() ) } type.storage.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - lastResult.ptRiskLevelResult.mostRecentDateAtRiskState, + lastResult.ptRiskLevelResult.mostRecentDateAtRiskState?.toJavaTime(), testRegisteredAt.toLocalDateUtc() ) } @@ -94,7 +96,7 @@ class AnalyticsKeySubmissionCollector @Inject constructor( fun reportSubmitted(type: BaseCoronaTest.Type) { if (disabled) return type.storage.submitted.update { true } - type.storage.submittedAt.update { timeStamper.nowUTC.millis } + type.storage.submittedAt.update { timeStamper.nowJavaUTC.toEpochMilli() } } fun reportSubmittedInBackground(type: BaseCoronaTest.Type) { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionDonor.kt index c9385fbbe0d..e9e7d23c44b 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionDonor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionDonor.kt @@ -5,8 +5,8 @@ import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.server.protocols.internal.ppdd.TriStateBooleanOuterClass import de.rki.coronawarnapp.util.TimeStamper -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import javax.inject.Inject import javax.inject.Singleton @@ -28,7 +28,7 @@ abstract class AnalyticsKeySubmissionDonor( ) : DonorModule { override suspend fun beginDonation(request: DonorModule.Request): DonorModule.Contribution { val hours = request.currentConfig.analytics.hoursSinceTestResultToSubmitKeySubmissionMetadata - val timeSinceTestResultToSubmit = Duration.standardHours(hours.toLong()) + val timeSinceTestResultToSubmit = Duration.ofHours(hours.toLong()) return if (shouldSubmitData(timeSinceTestResultToSubmit)) { object : DonorModule.Contribution { override suspend fun injectData(protobufContainer: PpaData.PPADataAndroid.Builder) { @@ -85,7 +85,9 @@ abstract class AnalyticsKeySubmissionDonor( @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) internal fun enoughTimeHasPassedSinceResult(timeSinceTestResultToSubmit: Duration): Boolean = - timeStamper.nowUTC.minus(timeSinceTestResultToSubmit) > Instant.ofEpochMilli(repository.testResultReceivedAt) + timeStamper + .nowJavaUTC + .minus(timeSinceTestResultToSubmit) > Instant.ofEpochMilli(repository.testResultReceivedAt) override suspend fun deleteData() { repository.reset() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt index f69b500d18b..f52445fd433 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionRepository.kt @@ -1,6 +1,6 @@ package de.rki.coronawarnapp.datadonation.analytics.modules.keysubmission -import org.joda.time.Duration +import java.time.Duration import javax.inject.Inject import javax.inject.Singleton @@ -58,7 +58,7 @@ abstract class AnalyticsKeySubmissionRepository( if (submittedAt <= 0) return -1 if (testResultReceivedAt <= 0) return -1 if (submittedAt < testResultReceivedAt) return -1 - return Duration.millis(submittedAt - testResultReceivedAt).toStandardHours().hours + return Duration.ofMillis(submittedAt - testResultReceivedAt).toHours().toInt() } val hoursSinceTestRegistration: Int @@ -66,7 +66,7 @@ abstract class AnalyticsKeySubmissionRepository( if (submittedAt <= 0) return -1 if (testRegisteredAt <= 0) return -1 if (submittedAt < testRegisteredAt) return -1 - return Duration.millis(submittedAt - testRegisteredAt).toStandardHours().hours + return Duration.ofMillis(submittedAt - testRegisteredAt).toHours().toInt() } val ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration: Int diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt index d81cc6c26fe..c14c8289e1e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollector.kt @@ -20,8 +20,11 @@ import de.rki.coronawarnapp.risk.result.RiskResult import de.rki.coronawarnapp.risk.storage.RiskLevelStorage import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc import de.rki.coronawarnapp.util.TimeStamper +import de.rki.coronawarnapp.util.toJavaInstant +import de.rki.coronawarnapp.util.toJavaTime +import de.rki.coronawarnapp.util.toLocalDateUtc import kotlinx.coroutines.flow.first -import org.joda.time.Duration +import java.time.Duration import timber.log.Timber import javax.inject.Inject @@ -46,7 +49,7 @@ class AnalyticsTestResultCollector @Inject constructor( suspend fun reportTestRegistered(type: BaseCoronaTest.Type) { if (analyticsDisabled) return - val testRegisteredAt = timeStamper.nowUTC + val testRegisteredAt = timeStamper.nowJavaUTC type.settings.testRegisteredAt.update { testRegisteredAt } val lastResult = riskLevelStorage @@ -56,14 +59,14 @@ class AnalyticsTestResultCollector @Inject constructor( type.settings.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toLocalDateUtc(), + lastResult.ewRiskLevelResult.mostRecentDateAtRiskState?.toJavaInstant()?.toLocalDateUtc(), testRegisteredAt.toLocalDateUtc() ) } type.settings.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration.update { calculateDaysSinceMostRecentDateAtRiskLevelAtTestRegistration( - lastResult.ptRiskLevelResult.mostRecentDateAtRiskState, + lastResult.ptRiskLevelResult.mostRecentDateAtRiskState?.toJavaTime(), testRegisteredAt.toLocalDateUtc() ) } @@ -72,10 +75,10 @@ class AnalyticsTestResultCollector @Inject constructor( riskLevelStorage.allEwRiskLevelResults .first() .getLastChangeToHighEwRiskBefore(testRegisteredAt)?.let { - val hours = Duration( + val hours = Duration.between( it, testRegisteredAt - ).standardHours.toInt() + ).toHours().toInt() type.settings.ewHoursSinceHighRiskWarningAtTestRegistration.update { hours } @@ -86,10 +89,10 @@ class AnalyticsTestResultCollector @Inject constructor( riskLevelStorage.allPtRiskLevelResults .first() .getLastChangeToHighPtRiskBefore(testRegisteredAt)?.let { - val hours = Duration( + val hours = Duration.between( it, testRegisteredAt - ).standardHours.toInt() + ).toHours().toInt() type.settings.ptHoursSinceHighRiskWarningAtTestRegistration.update { hours } @@ -128,7 +131,7 @@ class AnalyticsTestResultCollector @Inject constructor( type.settings.testResult.update { testResult } if (testResult.isFinalResult && type.settings.finalTestResultReceivedAt.value == null) { - type.settings.finalTestResultReceivedAt.update { timeStamper.nowUTC } + type.settings.finalTestResultReceivedAt.update { timeStamper.nowJavaUTC } val newExposureWindows = exposureWindowsSettings.currentExposureWindows.value?.filterExposureWindows( type.settings.exposureWindowsAtTestRegistration.value diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt index 68cc07103d8..a65c92f4ff2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultDonor.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.datadonation.analytics.modules.DonorModule import de.rki.coronawarnapp.datadonation.analytics.modules.exposurewindows.AnalyticsExposureWindow import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData import de.rki.coronawarnapp.util.TimeStamper -import org.joda.time.Duration +import java.time.Duration import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -50,10 +50,10 @@ abstract class AnalyticsTestResultDonor( return TestResultMetadataNoContribution } - val hoursSinceTestRegistrationTime = Duration( + val hoursSinceTestRegistrationTime = Duration.between( timestampAtRegistration, - testResultSettings.finalTestResultReceivedAt.value ?: timeStamper.nowUTC - ).standardHours.toInt() + testResultSettings.finalTestResultReceivedAt.value ?: timeStamper.nowJavaUTC + ).toHours().toInt() val configHours = request.currentConfig.analytics.hoursSinceTestRegistrationToSubmitTestResultMetadata val isDiffHoursMoreThanConfigHoursForPendingTest = hoursSinceTestRegistrationTime >= configHours diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt index 87f2c0d85e2..1ebf4c29ee2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettings.kt @@ -10,7 +10,7 @@ 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 org.joda.time.Instant +import java.time.Instant import javax.inject.Inject import javax.inject.Singleton @@ -45,7 +45,7 @@ open class AnalyticsTestResultSettings( } }, writer = { key, value -> - putLong(key, value?.millis ?: 0L) + putLong(key, value?.toEpochMilli() ?: 0L) } ) @@ -81,7 +81,7 @@ open class AnalyticsTestResultSettings( } }, writer = { key, value -> - putLong(key, value?.millis ?: 0L) + putLong(key, value?.toEpochMilli() ?: 0L) } ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt index 26593e15efb..2e14dc13e2a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/AnalyticsSettings.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.util.preferences.createFlowPreference import de.rki.coronawarnapp.util.reset.Resettable import okio.ByteString.Companion.decodeBase64 import okio.ByteString.Companion.toByteString -import org.joda.time.Instant +import java.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -83,7 +83,7 @@ class AnalyticsSettings @Inject constructor( } }, writer = { key, value -> - putLong(key, value?.millis ?: 0L) + putLong(key, value?.toEpochMilli() ?: 0L) } ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/LastAnalyticsSubmission.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/LastAnalyticsSubmission.kt index 11ba8b5c17c..82fe88a2b3e 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/LastAnalyticsSubmission.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/storage/LastAnalyticsSubmission.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.datadonation.analytics.storage import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData -import org.joda.time.Instant +import java.time.Instant data class LastAnalyticsSubmission( val timestamp: Instant, diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsTimeCalculation.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsTimeCalculation.kt index 11934e8b1c2..372972d61f8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsTimeCalculation.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsTimeCalculation.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.datadonation.analytics.worker import dagger.Reusable -import org.joda.time.Duration +import java.time.Duration import javax.inject.Inject import kotlin.random.Random @@ -11,5 +11,5 @@ class DataDonationAnalyticsTimeCalculation @Inject constructor() { * Get initial delay in hours for analytics one time submission work * currently there is no dependency on context but might appear later */ - fun getDelay(): Duration = Duration.standardHours(Random.nextLong(0, 24)) + fun getDelay(): Duration = Duration.ofHours(Random.nextLong(0, 24)) } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt index 4360da23c67..9e4aea04450 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/analytics/worker/DataDonationAnalyticsWorkBuilder.kt @@ -7,20 +7,16 @@ import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequestBuilder import dagger.Reusable import de.rki.coronawarnapp.worker.BackgroundConstants -import org.joda.time.DateTimeConstants -import org.joda.time.Duration +import java.time.Duration import java.util.concurrent.TimeUnit import javax.inject.Inject @Reusable class DataDonationAnalyticsWorkBuilder @Inject constructor() { fun buildPeriodicWork(initialDelay: Duration): PeriodicWorkRequest = - PeriodicWorkRequestBuilder( - DateTimeConstants.HOURS_PER_DAY.toLong(), - TimeUnit.HOURS - ) + PeriodicWorkRequestBuilder(Duration.ofDays(1)) .setInitialDelay( - initialDelay.standardHours, + initialDelay.toHours(), TimeUnit.HOURS ) .setBackoffCriteria( diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt index 2c18d17a561..88f8fdfdec8 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNet.kt @@ -13,11 +13,12 @@ import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.di.AppContext import de.rki.coronawarnapp.util.gplay.GoogleApiVersion import de.rki.coronawarnapp.util.security.RandomStrong +import de.rki.coronawarnapp.util.toJavaInstant import okio.ByteString import okio.ByteString.Companion.decodeHex import okio.ByteString.Companion.toByteString -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -111,15 +112,15 @@ class CWASafetyNet @Inject constructor( } val skip24hCheck = CWADebug.isDeviceForTestersBuild && testSettings.skipSafetyNetTimeCheck.value - val nowUTC = timeStamper.nowUTC - val firstReliableTimeStamp = cwaSettings.firstReliableDeviceTime - val timeSinceOnboarding = Duration(firstReliableTimeStamp, nowUTC) + val nowUTC = timeStamper.nowJavaUTC + val firstReliableTimeStamp = cwaSettings.firstReliableDeviceTime.toJavaInstant() + val timeSinceOnboarding = Duration.between(firstReliableTimeStamp, nowUTC) Timber.d("firstReliableTimeStamp=%s, now=%s", firstReliableTimeStamp, nowUTC) - Timber.d("skip24hCheck=%b, timeSinceOnboarding=%dh", skip24hCheck, timeSinceOnboarding.standardHours) + Timber.d("skip24hCheck=%b, timeSinceOnboarding=%dh", skip24hCheck, timeSinceOnboarding.toHours()) if (firstReliableTimeStamp == Instant.EPOCH) { throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "No first reliable timestamp available") - } else if (!skip24hCheck && timeSinceOnboarding < Duration.standardHours(24)) { + } else if (!skip24hCheck && timeSinceOnboarding < Duration.ofHours(24)) { throw SafetyNetException(Type.TIME_SINCE_ONBOARDING_UNVERIFIED, "Time since first reliable timestamp <24h") } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt index f792b7d7594..c43b3e26e5c 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/datadonation/survey/Surveys.kt @@ -7,6 +7,7 @@ import de.rki.coronawarnapp.datadonation.storage.OTPRepository import de.rki.coronawarnapp.datadonation.survey.server.SurveyServer import de.rki.coronawarnapp.util.TimeStamper import de.rki.coronawarnapp.util.coroutine.DispatcherProvider +import de.rki.coronawarnapp.util.toLocalDateUtc import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -70,10 +71,13 @@ class Surveys @Inject constructor( suspend fun requestDetails(type: Type): Survey { val config = appConfigProvider.getAppConfig().survey Timber.v("Requested survey: %s", config) - val now = timeStamper.nowUTC + val now = timeStamper.nowJavaUTC oneTimePasswordRepo.otpAuthorizationResult?.apply { - if (authorized && redeemedAt.toDateTime().monthOfYear() == now.toDateTime().monthOfYear()) { + if (authorized && + redeemedAt.toLocalDateUtc().month == now.toLocalDateUtc().month && + redeemedAt.toLocalDateUtc().year == now.toLocalDateUtc().year + ) { throw SurveyException(SurveyException.Type.ALREADY_PARTICIPATED_THIS_MONTH) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt index 6ed7e1d5524..9826429d5d2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/storage/OnboardingSettings.kt @@ -9,7 +9,7 @@ import de.rki.coronawarnapp.util.preferences.createFlowPreference import de.rki.coronawarnapp.util.reset.Resettable import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map -import org.joda.time.Instant +import java.time.Instant import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -31,7 +31,7 @@ class OnboardingSettings @Inject constructor( } else null }, writer = { key, value -> - putLong(key, value?.millis ?: 0L) + putLong(key, value?.toEpochMilli() ?: 0L) } ) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt index e5d1aca0d1f..478b06c3d63 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/onboarding/OnboardingActivity.kt @@ -70,7 +70,7 @@ class OnboardingActivity : AppCompatActivity(), LifecycleObserver, HasAndroidInj fun completeOnboarding() { onboardingSettings.onboardingCompletedTimestamp.update { - timeStamper.nowUTC + timeStamper.nowJavaUTC } settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } settings.lastChangelogVersion.update { BuildConfigWrap.VERSION_CODE } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JavaTimeAndDateExtenstions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JavaTimeAndDateExtenstions.kt index edb44ceb23a..e529832848a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JavaTimeAndDateExtenstions.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JavaTimeAndDateExtenstions.kt @@ -1,7 +1,10 @@ package de.rki.coronawarnapp.util import java.time.Instant +import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId +import java.time.ZoneOffset fun Instant.toUserTimeZone(): LocalDateTime = LocalDateTime.ofInstant(this, ZoneId.systemDefault()) +fun Instant.toLocalDateUtc(): LocalDate = atZone(ZoneOffset.UTC).toLocalDate() diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JodaMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JodaMigration.kt index 45810885d25..25ac6d96448 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JodaMigration.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/JodaMigration.kt @@ -1,3 +1,4 @@ package de.rki.coronawarnapp.util fun org.joda.time.LocalDate.toJavaTime(): java.time.LocalDate = java.time.LocalDate.of(year, monthOfYear, dayOfMonth) +fun org.joda.time.Instant.toJavaInstant(): java.time.Instant = java.time.Instant.ofEpochMilli(millis) diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/BaseJsonSerializer.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/BaseJsonSerializer.kt new file mode 100644 index 00000000000..75ded4079c2 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/BaseJsonSerializer.kt @@ -0,0 +1,26 @@ +package de.rki.coronawarnapp.util.datastore + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import com.fasterxml.jackson.databind.ObjectMapper +import timber.log.Timber +import java.io.InputStream +import java.io.OutputStream + +abstract class BaseJsonSerializer( + private val objectMapper: ObjectMapper +) : Serializer { + + private val type get() = defaultValue::class.java + + override suspend fun readFrom(input: InputStream): T = runCatching { objectMapper.readValue(input, type) } + .onFailure { throw CorruptionException("Failed to read data of type=$type", it) } + .getOrThrow() + + override suspend fun writeTo(t: T, output: OutputStream) { + runCatching { objectMapper.writeValue(output, t) } + .onFailure { Timber.tag(TAG).w(it, "Failed to write data=$t") } + } +} + +private const val TAG = "BaseJsonSerializer" diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensions.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensions.kt new file mode 100644 index 00000000000..b906ab78816 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensions.kt @@ -0,0 +1,72 @@ +package de.rki.coronawarnapp.util.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.emptyPreferences +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map +import timber.log.Timber +import java.io.IOException + +/** + * [DataStore] throws an [IOException] when an exception is encountered when reading data. This + * catches any [IOException] and recovers with [emptyPreferences]. + * Be aware that this can still throw other Exceptions. + */ +val DataStore.dataRecovering + get() = data.catch { + Timber.e(it, "Failed to read DataStore") + if (it is IOException) emit(emptyPreferences()) else throw it + } + +/** + * Returns a [Flow] containing the value set for the specified [key]. If no value is set for the + * key, the flow contains null. Uses [Preferences.get] to get the value. + * Be aware that this can emit the value multiple times, e.g. when another preferences gets changed. + */ +fun Flow.map(key: Preferences.Key) = map { prefs -> prefs[key] } + +/** + * Combines [map] to get the value for the specified [Preferences.Key] and + * [Flow.distinctUntilChanged] to filter out repetitions. + */ +fun Flow.distinctUntilChanged(key: Preferences.Key) = map(key) + .distinctUntilChanged() + +/** + * Returns the value set for the specified [Preferences.Key] or null, if the key is + * not set. + */ +suspend fun DataStore.getValueOrNull( + preferencesKey: Preferences.Key +): T? = dataRecovering.map(preferencesKey).firstOrNull() + +/** + * Returns the value set for the specified [Preferences.Key] or the [defaultValue], if the key is + * not set. + */ +suspend fun DataStore.getValueOrDefault( + preferencesKey: Preferences.Key, + defaultValue: T +): T = getValueOrNull(preferencesKey) ?: defaultValue + +/** + * Set the [value] for the specified [Preferences.Key] + */ +suspend fun DataStore.setValue( + preferencesKey: Preferences.Key, + value: T +) { + edit { prefs -> prefs[preferencesKey] = value } +} + +/** + * Deletes all data of the [DataStore] + */ +suspend fun DataStore.clear() { + edit { prefs -> prefs.clear() } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt index ef91ea4cccd..36e2345b72a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigration.kt @@ -11,7 +11,7 @@ import de.rki.coronawarnapp.storage.TracingSettings import de.rki.coronawarnapp.submission.SubmissionSettings import de.rki.coronawarnapp.util.TimeAndDateExtensions.toInstantOrNull import de.rki.coronawarnapp.util.di.AppContext -import org.joda.time.Instant +import java.time.Instant import timber.log.Timber import javax.inject.Inject diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/SerializationModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/SerializationModule.kt index 8e71fa8d095..4b81573819f 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/SerializationModule.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/serialization/SerializationModule.kt @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.datatype.joda.JodaModule +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jsonMapper import com.fasterxml.jackson.module.kotlin.kotlinModule @@ -46,7 +47,7 @@ class SerializationModule { .registerByteStringSerialization() jsonMapper { - addModules(kotlinModule(), JodaModule(), jacksonSerializationModule) + addModules(kotlinModule(), JodaModule(), JavaTimeModule(), jacksonSerializationModule) configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReportSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReportSettingsTest.kt deleted file mode 100644 index 7db2519dd9b..00000000000 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/BugReportSettingsTest.kt +++ /dev/null @@ -1,75 +0,0 @@ -package de.rki.coronawarnapp.bugreporting - -import android.content.Context -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.UploadHistory -import de.rki.coronawarnapp.util.serialization.SerializationModule -import io.kotest.matchers.shouldBe -import io.mockk.MockKAnnotations -import io.mockk.every -import io.mockk.impl.annotations.MockK -import java.time.Instant -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import testhelpers.BaseTest -import testhelpers.extensions.shouldMatchJson -import testhelpers.preferences.MockSharedPreferences - -class BugReportSettingsTest : BaseTest() { - @MockK lateinit var context: Context - lateinit var preferences: MockSharedPreferences - - private val baseGson = SerializationModule().baseGson() - - @BeforeEach - fun setup() { - MockKAnnotations.init(this) - preferences = MockSharedPreferences() - every { context.getSharedPreferences("bugreporting_localdata", Context.MODE_PRIVATE) } returns preferences - } - - fun createInstance() = BugReportingSettings( - context = context, - gson = baseGson - ) - - @Test - fun `upload history is empty by default`() { - val instance = createInstance() - preferences.dataMapPeek.isEmpty() shouldBe true - instance.uploadHistory.value shouldBe UploadHistory() - } - - @Test - fun `upload history save and load`() { - var expectedData: UploadHistory? = null - val instance = createInstance() - instance.uploadHistory.update { - it.copy( - logs = listOf( - LogUpload(id = "id1", uploadedAt = Instant.parse("2021-02-01T15:00:00.000Z")), - LogUpload(id = "id2", uploadedAt = Instant.parse("2021-02-02T15:00:00.000Z")) - ) - ).also { - expectedData = it - } - } - - preferences.dataMapPeek["upload.history"] as String shouldMatchJson """ - { - "logs": [ - { - "id": "id1", - "uploadedAt": 1612191600000 - }, - { - "id": "id2", - "uploadedAt": 1612278000000 - } - ] - } - """ - - instance.uploadHistory.value shouldBe expectedData - } -} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploaderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploaderTest.kt index d8a482e84f0..359c09ea917 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploaderTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/SnapshotUploaderTest.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.bugreporting.debuglog.upload -import de.rki.coronawarnapp.bugreporting.BugReportingSettings import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.UploadHistory +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.LogUploadServer import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadAuthorizer import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadOtp @@ -14,12 +14,13 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.verify +import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest -import testhelpers.preferences.mockFlowPreference +import testhelpers.preferences.FakeTypedDataStore import java.io.IOException class SnapshotUploaderTest : BaseTest() { @@ -27,16 +28,16 @@ class SnapshotUploaderTest : BaseTest() { @MockK lateinit var snapshotter: LogSnapshotter @MockK lateinit var uploadServer: LogUploadServer @MockK lateinit var authorizer: LogUploadAuthorizer - @MockK lateinit var bugReportingSettings: BugReportingSettings @MockK lateinit var snapshot: LogSnapshotter.Snapshot + private val dataStore = FakeTypedDataStore(UploadHistory(), shouldLog = true) + private val uploadHistoryStorage = UploadHistoryStorage(dataStore = dataStore) + private val logUploadOtp = LogUploadOtp( otp = "otp", expirationDate = Instant.EPOCH ) - private val uploadHistoryPref = mockFlowPreference(UploadHistory()) - private val expectedLogUpload = LogUpload( id = "123e4567-e89b-12d3-a456-426652340000", uploadedAt = Instant.parse("2020-08-20T23:00:00.000Z") @@ -46,7 +47,7 @@ class SnapshotUploaderTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - every { bugReportingSettings.uploadHistory } returns uploadHistoryPref + dataStore.reset() coEvery { authorizer.getAuthorizedOTP(otp = any()) } returns logUploadOtp coEvery { snapshotter.snapshot() } returns snapshot @@ -59,7 +60,7 @@ class SnapshotUploaderTest : BaseTest() { snapshotter = snapshotter, uploadServer = uploadServer, authorizer = authorizer, - bugReportingSettings = bugReportingSettings + uploadHistoryStorage = uploadHistoryStorage ) @Test @@ -67,7 +68,7 @@ class SnapshotUploaderTest : BaseTest() { val instance = createInstance() instance.uploadSnapshot() shouldBe expectedLogUpload - uploadHistoryPref.value shouldBe UploadHistory(logs = listOf(expectedLogUpload)) + uploadHistoryStorage.uploadHistory.first() shouldBe UploadHistory(logs = listOf(expectedLogUpload)) } @Test @@ -85,12 +86,12 @@ class SnapshotUploaderTest : BaseTest() { @Test fun `upload history is capped at 10`() = runTest { val existingEntries = (1..10L).map { LogUpload(id = "$it", Instant.ofEpochMilli(it)) } - uploadHistoryPref.update { UploadHistory(logs = existingEntries) } + uploadHistoryStorage.update { UploadHistory(logs = existingEntries) } val instance = createInstance() instance.uploadSnapshot() shouldBe expectedLogUpload - uploadHistoryPref.value shouldBe UploadHistory( + uploadHistoryStorage.uploadHistory.first() shouldBe UploadHistory( logs = existingEntries.subList( 1, 10 diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializerTest.kt new file mode 100644 index 00000000000..8efcbf305aa --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistorySerializerTest.kt @@ -0,0 +1,66 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import androidx.datastore.core.CorruptionException +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.extensions.shouldMatchJson +import java.io.ByteArrayOutputStream +import java.time.Instant + +class UploadHistorySerializerTest : BaseTest() { + + private val objectMapper = SerializationModule.jacksonBaseMapper + private val serializer = UploadHistorySerializer(objectMapper) + + private val testUploadHistory = UploadHistory( + logs = listOf( + LogUpload(id = "id1", uploadedAt = Instant.parse("2021-02-01T15:00:00.000Z")), + LogUpload(id = "id2", uploadedAt = Instant.parse("2021-02-02T15:00:00.000Z")) + ) + ) + + private val testUploadHistoryJson = """ + { + "logs": [ + { + "id": "id1", + "uploadedAt": 1612191600.000000000 + }, + { + "id": "id2", + "uploadedAt": 1612278000.000000000 + } + ] + } + """.trimIndent() + + @Test + fun `defaultValue is empty UploadHistory`() { + serializer.defaultValue shouldBe UploadHistory() + } + + @Test + fun `read UploadHistory`() = runTest { + testUploadHistoryJson + .byteInputStream() + .use { serializer.readFrom(it) } shouldBe testUploadHistory + } + + @Test + fun `write UploadHistory`() = runTest { + ByteArrayOutputStream() + .apply { use { serializer.writeTo(testUploadHistory, it) } } + .toString() shouldMatchJson testUploadHistoryJson + } + + @Test + fun `throws CorruptionException for invalid data`() = runTest { + shouldThrow { "Invalid Data".byteInputStream().use { serializer.readFrom(it) } } + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModuleTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModuleTest.kt new file mode 100644 index 00000000000..b371d20f4b6 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageModuleTest.kt @@ -0,0 +1,81 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import android.content.Context +import android.content.SharedPreferences +import androidx.core.content.edit +import androidx.datastore.migrations.SharedPreferencesMigration +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import de.rki.coronawarnapp.util.serialization.SerializationModule +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.preferences.MockSharedPreferences +import java.time.Instant + +class UploadHistoryStorageModuleTest : BaseTest() { + + @MockK lateinit var context: Context + + private lateinit var migration: SharedPreferencesMigration + private lateinit var sharedPrefs: SharedPreferences + private val gson = SerializationModule().baseGson() + + private val defaultUploadHistory = UploadHistory() + private val testUploadHistory = UploadHistory( + logs = listOf( + LogUpload(id = "id1", uploadedAt = Instant.parse("2021-02-01T15:00:00.000Z")), + LogUpload(id = "id2", uploadedAt = Instant.parse("2021-02-02T15:00:00.000Z")) + ) + ) + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + sharedPrefs = MockSharedPreferences() + every { context.getSharedPreferences(any(), any()) } returns sharedPrefs + + migration = UploadHistoryStorageModule.provideMigration(context, gson) + } + + @Test + fun `migration success`() = runTest { + val testUploadHistoryJson = """ + { + "logs": [ + { + "id": "id1", + "uploadedAt": 1612191600000 + }, + { + "id": "id2", + "uploadedAt": 1612278000000 + } + ] + } + """.trimIndent() + + sharedPrefs.edit(true) { + putString(LEGACY_UPLOAD_HISTORY_KEY, testUploadHistoryJson) + } + + val migratedHistory = migration.migrate(defaultUploadHistory) + migratedHistory shouldBe testUploadHistory + } + + @Test + fun `returns default if migration fails`() = runTest { + sharedPrefs.edit(true) { + putString(LEGACY_UPLOAD_HISTORY_KEY, "Invalid Data") + } + + val migratedHistory = migration.migrate(defaultUploadHistory) + migratedHistory shouldBe defaultUploadHistory + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageTest.kt new file mode 100644 index 00000000000..76886e5645c --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/history/storage/UploadHistoryStorageTest.kt @@ -0,0 +1,63 @@ +package de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage + +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.UploadHistory +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest +import testhelpers.preferences.FakeTypedDataStore +import java.time.Instant + +class UploadHistoryStorageTest : BaseTest() { + + private val defaultUploadHistory = UploadHistory() + + private val dataStore = FakeTypedDataStore(defaultValue = defaultUploadHistory, shouldLog = true) + private val uploadHistoryStorage = UploadHistoryStorage(dataStore) + + private val testLogs = listOf( + LogUpload(id = "id1", uploadedAt = Instant.parse("2021-02-01T15:00:00.000Z")), + LogUpload(id = "id2", uploadedAt = Instant.parse("2021-02-02T15:00:00.000Z")) + ) + + private val testUploadHistory = UploadHistory(logs = testLogs) + + @BeforeEach + fun setup() { + dataStore.reset() + } + + @Test + fun `upload history is empty by default`() = runTest { + uploadHistoryStorage.uploadHistory.first() shouldBe defaultUploadHistory + } + + @Test + fun `upload history is loaded from DataStore`() = runTest { + dataStore.updateData { testUploadHistory } + + uploadHistoryStorage.uploadHistory.first() shouldBe testUploadHistory + } + + @Test + fun `upload history save and load`() = runTest { + uploadHistoryStorage.update { it.copy(logs = testLogs) } + + uploadHistoryStorage.uploadHistory.first() shouldBe testUploadHistory + dataStore.data.first() shouldBe testUploadHistory + } + + @Test + fun `upload history reset`() = runTest { + uploadHistoryStorage.update { it.copy(logs = testLogs) } + uploadHistoryStorage.uploadHistory.first() shouldBe testUploadHistory + dataStore.data.first() shouldBe testUploadHistory + + uploadHistoryStorage.reset() + uploadHistoryStorage.uploadHistory.first() shouldBe defaultUploadHistory + dataStore.data.first() shouldBe defaultUploadHistory + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServerTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServerTest.kt index 938cec26778..4abe06ba53a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServerTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/debuglog/upload/server/LogUploadServerTest.kt @@ -1,7 +1,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog.upload.server import de.rki.coronawarnapp.bugreporting.debuglog.internal.LogSnapshotter -import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.LogUpload +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.model.LogUpload import de.rki.coronawarnapp.bugreporting.debuglog.upload.server.auth.LogUploadOtp import de.rki.coronawarnapp.util.TimeStamper import io.kotest.matchers.shouldBe diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt index 80f5779b563..1315203dded 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/AnalyticsTest.kt @@ -32,12 +32,12 @@ import io.mockk.slot import io.mockk.spyk import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest -import org.joda.time.Days -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest import testhelpers.preferences.mockFlowPreference +import java.time.Duration class AnalyticsTest : BaseTest() { @MockK lateinit var dataDonationAnalyticsServer: DataDonationAnalyticsServer @@ -62,14 +62,14 @@ class AnalyticsTest : BaseTest() { coEvery { lastAnalyticsSubmissionLogger.storeAnalyticsData(any()) } just Runs - every { timeStamper.nowUTC } returns baseTime + every { timeStamper.nowJavaUTC } returns baseTime every { analyticsConfig.analyticsEnabled } returns true every { settings.analyticsEnabled } returns mockFlowPreference(true) every { analyticsConfig.probabilityToSubmit } returns 1.0 - val twoDaysAgo = baseTime.minus(Days.TWO.toStandardDuration()) + val twoDaysAgo = baseTime.minus(Duration.ofDays(2)) every { settings.lastSubmittedTimestamp } returns mockFlowPreference(twoDaysAgo) every { onboardingSettings.onboardingCompletedTimestamp } returns mockFlowPreference(twoDaysAgo) @@ -224,7 +224,7 @@ class AnalyticsTest : BaseTest() { fun `submit analytics data`() { val metadata = PpaData.ExposureRiskMetadata.newBuilder() .setRiskLevel(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) - .setMostRecentDateAtRiskLevel(baseTime.millis) + .setMostRecentDateAtRiskLevel(baseTime.toEpochMilli()) .setDateChangedComparedToPreviousSubmission(true) .setRiskLevelChangedComparedToPreviousSubmission(true) .build() diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt index ce2436dfc3f..0745b92251b 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/CalculationsTest.kt @@ -5,6 +5,7 @@ import de.rki.coronawarnapp.presencetracing.risk.PtRiskLevelResult import de.rki.coronawarnapp.risk.EwRiskLevelResult import de.rki.coronawarnapp.risk.RiskState import de.rki.coronawarnapp.risk.result.EwAggregatedRiskResult +import de.rki.coronawarnapp.util.toJavaInstant import io.kotest.matchers.shouldBe import org.joda.time.Instant import org.joda.time.LocalDate @@ -53,8 +54,8 @@ class CalculationsTest : BaseTest() { ptRisk3, ptRisk4, ptRisk5 - ).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant() + ).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test @@ -97,8 +98,8 @@ class CalculationsTest : BaseTest() { ptRisk3, ptRisk4, ptRisk5 - ).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 20).toDateTimeAtStartOfDay().toInstant() + ).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 20).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test @@ -141,8 +142,8 @@ class CalculationsTest : BaseTest() { ptRisk3, ptRisk4, ptRisk5 - ).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant() + ).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test @@ -185,7 +186,7 @@ class CalculationsTest : BaseTest() { ptRisk3, ptRisk4, ptRisk5 - ).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe + ).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe null } @@ -197,14 +198,14 @@ class CalculationsTest : BaseTest() { calculatedFrom = LocalDate(2021, 3, 5).toDateTimeAtStartOfDay().toInstant() ) val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant() - listOf(ptRisk0).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe + listOf(ptRisk0).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe null } @Test fun `getLastChangeToHighRiskPt works 6`() { val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant() - listOf().getLastChangeToHighPtRiskBefore(registeredAt) shouldBe + listOf().getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe null } @@ -216,8 +217,8 @@ class CalculationsTest : BaseTest() { calculatedFrom = LocalDate(2021, 3, 5).toDateTimeAtStartOfDay().toInstant() ) val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant() - listOf(ptRisk0).getLastChangeToHighPtRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant() + listOf(ptRisk0).getLastChangeToHighPtRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test @@ -227,8 +228,8 @@ class CalculationsTest : BaseTest() { riskState = RiskState.INCREASED_RISK ) val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant() - listOf(ewRisk0).getLastChangeToHighEwRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant() + listOf(ewRisk0).getLastChangeToHighEwRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 19).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test @@ -265,14 +266,14 @@ class CalculationsTest : BaseTest() { risk3, risk4, risk5 - ).getLastChangeToHighEwRiskBefore(registeredAt) shouldBe - LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant() + ).getLastChangeToHighEwRiskBefore(registeredAt.toJavaInstant()) shouldBe + LocalDate(2021, 3, 22).toDateTimeAtStartOfDay().toInstant().toJavaInstant() } @Test fun `getLastChangeToHighEwRiskBefore works 3`() { val registeredAt = LocalDate(2021, 3, 23).toDateTimeAtStartOfDay().toInstant() - listOf().getLastChangeToHighEwRiskBefore(registeredAt) shouldBe + listOf().getLastChangeToHighEwRiskBefore(registeredAt.toJavaInstant()) shouldBe null } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt index a5ee7537018..9e134114844 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/common/PpaDataExtensionsTest.kt @@ -1,9 +1,9 @@ package de.rki.coronawarnapp.datadonation.analytics.common import de.rki.coronawarnapp.server.protocols.internal.ppdd.PpaData -import de.rki.coronawarnapp.util.TimeAndDateExtensions.toLocalDateUtc +import de.rki.coronawarnapp.util.toLocalDateUtc import io.kotest.matchers.shouldBe -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.Test import testhelpers.BaseTest diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowsRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowsRepositoryTest.kt index b6fb1cb58bc..b0667536c5e 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowsRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/exposurewindows/AnalyticsExposureWindowsRepositoryTest.kt @@ -11,11 +11,11 @@ import io.mockk.every import io.mockk.impl.annotations.MockK import io.mockk.just import kotlinx.coroutines.test.runTest -import org.joda.time.Days -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest +import java.time.Duration class AnalyticsExposureWindowsRepositoryTest : BaseTest() { @@ -45,7 +45,7 @@ class AnalyticsExposureWindowsRepositoryTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns now + every { timeStamper.nowJavaUTC } returns now coEvery { analyticsExposureWindowDao.deleteReportedOlderThan(any()) } just Runs } @@ -56,7 +56,7 @@ class AnalyticsExposureWindowsRepositoryTest : BaseTest() { newInstance().deleteStaleData() coVerify { analyticsExposureWindowDao.deleteReportedOlderThan( - now.minus(Days.days(15).toStandardDuration()).millis + now.minus(Duration.ofDays(15)).toEpochMilli() ) } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt index 7a7a74bf3d2..ec4908fb49a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsKeySubmissionCollectorTest.kt @@ -21,7 +21,7 @@ import io.mockk.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import org.joda.time.Days -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -38,11 +38,12 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { @MockK lateinit var ptRiskLevelResult: PtRiskLevelResult private val now = Instant.now() + private val nowJoda = org.joda.time.Instant.now() @BeforeEach fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns now + every { timeStamper.nowJavaUTC } returns now } @Test @@ -52,8 +53,8 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { every { ewRiskLevelResult.riskState } returns RiskState.INCREASED_RISK every { ptRiskLevelResult.riskState } returns RiskState.LOW_RISK - every { ewRiskLevelResult.calculatedAt } returns now - every { ptRiskLevelResult.calculatedAt } returns now + every { ewRiskLevelResult.calculatedAt } returns nowJoda + every { ptRiskLevelResult.calculatedAt } returns nowJoda every { ewRiskLevelResult.wasSuccessfullyCalculated } returns true every { ptRiskLevelResult.wasSuccessfullyCalculated } returns true @@ -65,10 +66,10 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { coEvery { riskLevelStorage.allEwRiskLevelResults } returns flowOf(listOf(ewRiskLevelResult)) coEvery { riskLevelStorage.allPtRiskLevelResults } returns flowOf(listOf(ptRiskLevelResult)) - val pcrTestRegisteredAt = mockFlowPreference(now.millis) + val pcrTestRegisteredAt = mockFlowPreference(now.toEpochMilli()) coEvery { analyticsPcrKeySubmissionStorage.testRegisteredAt } returns pcrTestRegisteredAt - val raTestRegisteredAt = mockFlowPreference(now.millis) + val raTestRegisteredAt = mockFlowPreference(now.toEpochMilli()) coEvery { analyticsRaKeySubmissionStorage.testRegisteredAt } returns raTestRegisteredAt every { ewRiskLevelResult.wasSuccessfullyCalculated } returns true @@ -92,9 +93,9 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { every { analyticsPcrKeySubmissionStorage.clear() } just Runs every { analyticsRaKeySubmissionStorage.clear() } just Runs - every { ewRiskLevelResult.mostRecentDateAtRiskState } returns now.minus(Days.days(2).toStandardDuration()) + every { ewRiskLevelResult.mostRecentDateAtRiskState } returns nowJoda.minus(Days.days(2).toStandardDuration()) every { ptRiskLevelResult.mostRecentDateAtRiskState } returns - now.minus(Days.days(2).toStandardDuration()).toLocalDateUtc() + nowJoda.minus(Days.days(2).toStandardDuration()).toLocalDateUtc() runTest { val collector = createInstance() @@ -118,7 +119,7 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { coEvery { analyticsSettings.analyticsEnabled.value } returns true val submittedFlow = mockFlowPreference(false) every { analyticsPcrKeySubmissionStorage.submitted } returns submittedFlow - val submittedAtFlow = mockFlowPreference(now.millis) + val submittedAtFlow = mockFlowPreference(now.toEpochMilli()) every { analyticsPcrKeySubmissionStorage.submittedAt } returns submittedAtFlow runTest { val collector = createInstance() @@ -180,7 +181,7 @@ class AnalyticsKeySubmissionCollectorTest : BaseTest() { @Test fun `PCR positive test result received is not overwritten`() { coEvery { analyticsSettings.analyticsEnabled.value } returns true - val flow = mockFlowPreference(now.millis) + val flow = mockFlowPreference(now.toEpochMilli()) every { analyticsPcrKeySubmissionStorage.testResultReceivedAt } returns flow runTest { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsPCRKeySubmissionDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsPCRKeySubmissionDonorTest.kt index cb7f5b27273..2fb15535ea5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsPCRKeySubmissionDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsPCRKeySubmissionDonorTest.kt @@ -13,8 +13,8 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.verify import kotlinx.coroutines.test.runTest -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -34,7 +34,7 @@ class AnalyticsPCRKeySubmissionDonorTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns now + every { timeStamper.nowJavaUTC } returns now every { configData.analytics.hoursSinceTestResultToSubmitKeySubmissionMetadata } returns 6 } @@ -50,7 +50,7 @@ class AnalyticsPCRKeySubmissionDonorTest : BaseTest() { @Test fun `no contribution when neither submitted nor enough time passed`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.submitted } returns false runTest { val donor = createInstance() @@ -60,7 +60,7 @@ class AnalyticsPCRKeySubmissionDonorTest : BaseTest() { @Test fun `regular contribution when keys submitted`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.advancedConsentGiven } returns true every { repository.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns 1 every { repository.ewHoursSinceHighRiskWarningAtTestRegistration } returns 1 @@ -92,12 +92,12 @@ class AnalyticsPCRKeySubmissionDonorTest : BaseTest() { @Test fun `submit contribution after enough time has passed`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.submitted } returns true - val minTimePassedToSubmit = Duration.standardHours(3) + val minTimePassedToSubmit = Duration.ofHours(3) runTest { val donor = createInstance() - donor.enoughTimeHasPassedSinceResult(Duration.standardHours(3)) shouldBe true + donor.enoughTimeHasPassedSinceResult(Duration.ofHours(3)) shouldBe true donor.shouldSubmitData(minTimePassedToSubmit) shouldBe true } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsRAKeySubmissionDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsRAKeySubmissionDonorTest.kt index 4de8c17fc69..97073d24dc5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsRAKeySubmissionDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/keysubmission/AnalyticsRAKeySubmissionDonorTest.kt @@ -13,8 +13,8 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.verify import kotlinx.coroutines.test.runTest -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -34,7 +34,7 @@ class AnalyticsRAKeySubmissionDonorTest : BaseTest() { @BeforeEach fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns now + every { timeStamper.nowJavaUTC } returns now every { configData.analytics.hoursSinceTestResultToSubmitKeySubmissionMetadata } returns 6 } @@ -50,7 +50,7 @@ class AnalyticsRAKeySubmissionDonorTest : BaseTest() { @Test fun `no contribution when neither submitted nor enough time passed`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.submitted } returns false runTest { val donor = createInstance() @@ -60,7 +60,7 @@ class AnalyticsRAKeySubmissionDonorTest : BaseTest() { @Test fun `regular contribution when keys submitted`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.advancedConsentGiven } returns true every { repository.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns 1 every { repository.ptDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns 1 @@ -92,12 +92,12 @@ class AnalyticsRAKeySubmissionDonorTest : BaseTest() { @Test fun `submit contribution after enough time has passed`() { - every { repository.testResultReceivedAt } returns now.minus(Duration.standardHours(4)).millis + every { repository.testResultReceivedAt } returns now.minus(Duration.ofHours(4)).toEpochMilli() every { repository.submitted } returns true - val minTimePassedToSubmit = Duration.standardHours(3) + val minTimePassedToSubmit = Duration.ofHours(3) runTest { val donor = createInstance() - donor.enoughTimeHasPassedSinceResult(Duration.standardHours(3)) shouldBe true + donor.enoughTimeHasPassedSinceResult(Duration.ofHours(3)) shouldBe true donor.shouldSubmitData(minTimePassedToSubmit) shouldBe true } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt index 6cf5a83b849..cb06b51f444 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsPCRTestResultDonorTest.kt @@ -18,8 +18,8 @@ import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verify import kotlinx.coroutines.test.runTest -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -68,7 +68,7 @@ class AnalyticsPCRTestResultDonorTest : BaseTest() { every { exposureWindowsUntilTestResult } returns mockFlowPreference(listOf(analyticsExposureWindow, analyticsExposureWindow)) } - every { timeStamper.nowUTC } returns baseTime + every { timeStamper.nowJavaUTC } returns baseTime testResultDonor = AnalyticsPCRTestResultDonor( testResultSettings, @@ -118,7 +118,7 @@ class AnalyticsPCRTestResultDonorTest : BaseTest() { runTest { every { testResultSettings.testResult } returns mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING) - val timeDayBefore = baseTime.minus(Duration.standardDays(1)) + val timeDayBefore = baseTime.minus(Duration.ofDays(1)) every { testResultSettings.testRegisteredAt } returns mockFlowPreference(timeDayBefore) every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(null) @@ -193,7 +193,7 @@ class AnalyticsPCRTestResultDonorTest : BaseTest() { Instant.parse("2021-03-20T00:00:00Z") ) } - every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { timeStamper.nowJavaUTC } returns Instant.parse("2021-03-20T00:00:00Z") val donation = testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution @@ -221,7 +221,7 @@ class AnalyticsPCRTestResultDonorTest : BaseTest() { every { ewRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) } - every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { timeStamper.nowJavaUTC } returns Instant.parse("2021-03-20T00:00:00Z") val donation = testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt index b13b0bbec14..7e2b9093be0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsRATestResultDonorTest.kt @@ -18,8 +18,8 @@ import io.mockk.mockk import io.mockk.unmockkAll import io.mockk.verify import kotlinx.coroutines.test.runTest -import org.joda.time.Duration -import org.joda.time.Instant +import java.time.Duration +import java.time.Instant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -66,7 +66,7 @@ class AnalyticsRATestResultDonorTest : BaseTest() { mockFlowPreference(listOf(analyticsExposureWindow, analyticsExposureWindow)) every { exposureWindowsUntilTestResult } returns mockFlowPreference(listOf(analyticsExposureWindow)) } - every { timeStamper.nowUTC } returns baseTime + every { timeStamper.nowJavaUTC } returns baseTime testResultDonor = AnalyticsRATestResultDonor( testResultSettings, @@ -116,7 +116,7 @@ class AnalyticsRATestResultDonorTest : BaseTest() { runTest { every { testResultSettings.testResult } returns mockFlowPreference(CoronaTestResult.PCR_OR_RAT_PENDING) - val timeDayBefore = baseTime.minus(Duration.standardDays(1)) + val timeDayBefore = baseTime.minus(Duration.ofDays(1)) every { testResultSettings.testRegisteredAt } returns mockFlowPreference(timeDayBefore) every { testResultSettings.finalTestResultReceivedAt } returns mockFlowPreference(null) @@ -189,7 +189,7 @@ class AnalyticsRATestResultDonorTest : BaseTest() { Instant.parse("2021-03-20T00:00:00Z") ) } - every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { timeStamper.nowJavaUTC } returns Instant.parse("2021-03-20T00:00:00Z") val donation = testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution @@ -217,7 +217,7 @@ class AnalyticsRATestResultDonorTest : BaseTest() { every { ewRiskLevelAtTestRegistration } returns mockFlowPreference(PpaData.PPARiskLevel.RISK_LEVEL_HIGH) } - every { timeStamper.nowUTC } returns Instant.parse("2021-03-20T00:00:00Z") + every { timeStamper.nowJavaUTC } returns Instant.parse("2021-03-20T00:00:00Z") val donation = testResultDonor.beginDonation(TestRequest) as AnalyticsTestResultDonor.TestResultMetadataContribution diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt index dceda93ffd1..d5b0489d5e7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultCollectorTest.kt @@ -32,11 +32,12 @@ import io.mockk.just import io.mockk.verify import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest import testhelpers.preferences.mockFlowPreference +import java.time.OffsetDateTime class AnalyticsTestResultCollectorTest : BaseTest() { @@ -59,7 +60,7 @@ class AnalyticsTestResultCollectorTest : BaseTest() { fun setup() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns Instant.parse("2021-03-02T09:57:11+01:00") + every { timeStamper.nowJavaUTC } returns OffsetDateTime.parse("2021-03-02T09:57:11+01:00").toInstant() every { pcrTestResultSettings.clear() } just Runs every { raTestResultSettings.clear() } just Runs @@ -68,13 +69,14 @@ class AnalyticsTestResultCollectorTest : BaseTest() { every { combinedResult.ptRiskLevelResult } returns ptRiskLevelResult every { ewRiskLevelResult.riskState } returns RiskState.LOW_RISK every { ptRiskLevelResult.riskState } returns RiskState.LOW_RISK - every { ewRiskLevelResult.mostRecentDateAtRiskState } returns Instant.parse("2021-03-02T09:57:11+01:00") + every { ewRiskLevelResult.mostRecentDateAtRiskState } returns + org.joda.time.Instant.parse("2021-03-02T09:57:11+01:00") every { ptRiskLevelResult.mostRecentDateAtRiskState } returns - Instant.parse("2021-03-02T09:57:11+01:00").toLocalDateUtc() + org.joda.time.Instant.parse("2021-03-02T09:57:11+01:00").toLocalDateUtc() every { riskLevelStorage.latestAndLastSuccessfulCombinedEwPtRiskLevelResult } returns flowOf(lastCombinedResults) every { exposureWindowsSettings.currentExposureWindows } returns mockFlowPreference(null) - every { pcrTestResultSettings.testRegisteredAt } returns mockFlowPreference(timeStamper.nowUTC) + every { pcrTestResultSettings.testRegisteredAt } returns mockFlowPreference(timeStamper.nowJavaUTC) every { pcrTestResultSettings.exposureWindowsAtTestRegistration } returns mockFlowPreference(emptyList()) every { pcrTestResultSettings.ewDaysSinceMostRecentDateAtRiskLevelAtTestRegistration } returns mockFlowPreference(1) @@ -225,7 +227,7 @@ class AnalyticsTestResultCollectorTest : BaseTest() { every { analyticsSettings.analyticsEnabled } returns mockFlowPreference(true) every { pcrTestResultSettings.testResult } returns mockFlowPreference(PCR_OR_RAT_PENDING) every { pcrTestResultSettings.finalTestResultReceivedAt } returns - mockFlowPreference(Instant.parse("2021-03-02T09:57:11+01:00")) + mockFlowPreference(OffsetDateTime.parse("2021-03-02T09:57:11+01:00").toInstant()) analyticsTestResultCollector.reportTestResultReceived(PCR_NEGATIVE, PCR) verify { @@ -237,7 +239,7 @@ class AnalyticsTestResultCollectorTest : BaseTest() { every { raTestResultSettings.testResult } returns mockFlowPreference(PCR_OR_RAT_PENDING) every { raTestResultSettings.finalTestResultReceivedAt } returns - mockFlowPreference(Instant.parse("2021-03-02T09:57:11+01:00")) + mockFlowPreference(OffsetDateTime.parse("2021-03-02T09:57:11+01:00").toInstant()) analyticsTestResultCollector.reportTestResultReceived(RAT_NEGATIVE, RAPID_ANTIGEN) verify { diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt index 3d2560d1bd6..7055caa4b09 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/analytics/modules/testresult/AnalyticsTestResultSettingsTest.kt @@ -11,7 +11,7 @@ import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt index d7007195c94..224ef49f8a5 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/safetynet/CWASafetyNetTest.kt @@ -28,12 +28,12 @@ import io.mockk.mockk import io.mockk.mockkObject import kotlinx.coroutines.test.runTest import okio.ByteString.Companion.decodeBase64 -import org.joda.time.Duration -import org.joda.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest import testhelpers.preferences.mockFlowPreference +import java.time.Duration +import java.time.Instant import kotlin.random.Random class CWASafetyNetTest : BaseTest() { @@ -82,8 +82,12 @@ class CWASafetyNetTest : BaseTest() { coEvery { appConfigProvider.getAppConfig() } returns appConfigData every { appConfigData.deviceTimeState } returns ConfigData.DeviceTimeState.CORRECT - every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH.plus(Duration.standardDays(7)) - every { timeStamper.nowUTC } returns Instant.EPOCH.plus(Duration.standardDays(8)) + every { cwaSettings.firstReliableDeviceTime } returns org.joda.time.Instant.EPOCH.plus( + org.joda.time.Duration.standardDays( + 7 + ) + ) + every { timeStamper.nowJavaUTC } returns Instant.EPOCH.plus(Duration.ofDays(8)) every { testSettings.skipSafetyNetTimeCheck } returns mockFlowPreference(false) } @@ -221,7 +225,7 @@ class CWASafetyNetTest : BaseTest() { @Test fun `first reliable devicetime timestamp needs to be more than 24 hours ago`() = runTest { - every { timeStamper.nowUTC } returns Instant.EPOCH + every { timeStamper.nowJavaUTC } returns Instant.EPOCH val exception = shouldThrow { createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) } @@ -230,7 +234,7 @@ class CWASafetyNetTest : BaseTest() { @Test fun `24h since onboarding can be skipped on deviceForTester builds`() = runTest { - every { timeStamper.nowUTC } returns Instant.EPOCH + every { timeStamper.nowJavaUTC } returns Instant.EPOCH shouldThrow { createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) @@ -251,7 +255,7 @@ class CWASafetyNetTest : BaseTest() { @Test fun `first reliable devicetime timestamp needs to be set`() = runTest { - every { cwaSettings.firstReliableDeviceTime } returns Instant.EPOCH + every { cwaSettings.firstReliableDeviceTime } returns org.joda.time.Instant.EPOCH val exception = shouldThrow { createInstance().attest(TestAttestationRequest("Computer says no.".toByteArray())) } @@ -261,7 +265,7 @@ class CWASafetyNetTest : BaseTest() { @Test fun `device time checks can be disabled via request`() = runTest { every { appConfigData.deviceTimeState } returns ConfigData.DeviceTimeState.ASSUMED_CORRECT - every { timeStamper.nowUTC } returns Instant.EPOCH + every { timeStamper.nowJavaUTC } returns Instant.EPOCH val request = TestAttestationRequest( "Computer says no.".toByteArray(), diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt index 08a05cafd8f..62bd68cc151 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/storage/OTPRepositoryTest.kt @@ -10,7 +10,7 @@ import io.kotest.matchers.shouldNotBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -36,7 +36,7 @@ class OTPRepositoryTest : BaseTest() { lastOTP shouldNotBe null lastOTP!!.apply { uuid shouldBe uuid - time.millis shouldBe 1612381131014 + time.toEpochMilli() shouldBe 1612381131014 } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt index cb10d10b7f7..5d8926936b0 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveySettingsTest.kt @@ -10,7 +10,7 @@ import io.kotest.matchers.shouldNotBe import io.mockk.MockKAnnotations import io.mockk.every import io.mockk.impl.annotations.MockK -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -50,7 +50,7 @@ class SurveySettingsTest : BaseTest() { val value = instance.oneTimePassword value shouldNotBe null value!!.uuid.toString() shouldBe "e103c755-0975-4588-a639-d0cd1ba421a1" - value.time.millis shouldBe 1612381217442 + value.time.toEpochMilli() shouldBe 1612381217442 } @Test @@ -104,7 +104,7 @@ class SurveySettingsTest : BaseTest() { value shouldNotBe null value!!.uuid.toString() shouldBe "e103c755-0975-4588-a639-d0cd1ba421a1" value.authorized shouldBe true - value.redeemedAt.millis shouldBe 1612381217443 + value.redeemedAt.toEpochMilli() shouldBe 1612381217443 value.invalidated shouldBe true } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveysTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveysTest.kt index a80165abba7..dba2f0d0512 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveysTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/datadonation/survey/SurveysTest.kt @@ -17,7 +17,7 @@ import io.mockk.coEvery import io.mockk.every import io.mockk.impl.annotations.MockK import kotlinx.coroutines.test.runTest -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import testhelpers.BaseTest @@ -36,7 +36,7 @@ internal class SurveysTest : BaseTest() { @BeforeEach fun setUp() { MockKAnnotations.init(this) - every { timeStamper.nowUTC } returns Instant.parse("2020-01-01T00:00:00.000Z") + every { timeStamper.nowJavaUTC } returns Instant.parse("2020-01-01T00:00:00.000Z") } private fun createInstance() = Surveys( @@ -60,7 +60,7 @@ internal class SurveysTest : BaseTest() { every { oneTimePasswordRepo.otpAuthorizationResult } returns OTPAuthorizationResult( UUID.randomUUID(), authorized = false, - redeemedAt = timeStamper.nowUTC, + redeemedAt = timeStamper.nowJavaUTC, invalidated = false ) createInstance().isConsentNeeded(HIGH_RISK_ENCOUNTER) shouldBe Needed @@ -72,7 +72,7 @@ internal class SurveysTest : BaseTest() { every { oneTimePasswordRepo.otpAuthorizationResult } returns OTPAuthorizationResult( UUID.randomUUID(), authorized = true, - redeemedAt = timeStamper.nowUTC, + redeemedAt = timeStamper.nowJavaUTC, invalidated = true ) createInstance().isConsentNeeded(HIGH_RISK_ENCOUNTER) shouldBe Needed @@ -84,7 +84,7 @@ internal class SurveysTest : BaseTest() { every { oneTimePasswordRepo.otpAuthorizationResult } returns OTPAuthorizationResult( UUID.randomUUID(), authorized = true, - redeemedAt = timeStamper.nowUTC, + redeemedAt = timeStamper.nowJavaUTC, invalidated = false ) coEvery { urlProvider.provideUrl(any(), any()) } returns "" diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensionsTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensionsTest.kt new file mode 100644 index 00000000000..79994fa45bf --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/datastore/DataStoreExtensionsTest.kt @@ -0,0 +1,89 @@ +package de.rki.coronawarnapp.util.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.emptyPreferences +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseIOTest +import testhelpers.preferences.FakeDataStore + +class DataStoreExtensionsTest : BaseIOTest() { + + private lateinit var dataStore: DataStore + private val stringPref = stringPreferencesKey("string_test_key") + private val intPref = intPreferencesKey("int_test_key") + + @BeforeEach + fun setup() { + dataStore = FakeDataStore() + } + + @Test + fun `get and set value`() = runTest { + val default = "default" + val newValue = "newValue" + val newValue2 = "newValue2" + + dataStore.getValueOrNull(stringPref) shouldBe null + dataStore.getValueOrDefault(stringPref, default) shouldBe default + + dataStore.setValue(stringPref, newValue) + dataStore.getValueOrNull(stringPref) shouldBe newValue + dataStore.getValueOrDefault(stringPref, default) shouldBe newValue + + dataStore.setValue(stringPref, newValue2) + dataStore.getValueOrNull(stringPref) shouldBe newValue2 + dataStore.getValueOrDefault(stringPref, default) shouldBe newValue2 + } + + @Test + fun `preferences key mapping`() = runTest { + val ints = listOf(0, 1, 2, 3, 4, 5, 6) + + flow { + val prefs = emptyPreferences().toMutablePreferences() + for (i in ints) { + prefs[intPref] = i + emit(prefs) + } + }.map(intPref).toList() shouldBe ints + } + + @Test + fun `preferences key mapping distinctUntilChanged`() = runTest { + val ints = listOf(0, 0, 1, 1, 2, 2, 2, 3, 4, 5, 6) + val distinct = ints.distinct() + + flow { + val prefs = emptyPreferences().toMutablePreferences() + for (i in ints) { + prefs[intPref] = i + emit(prefs) + } + }.distinctUntilChanged(intPref).toList() shouldBe distinct + } + + @Test + fun `clears all data`() = runTest { + val stringValue = "stringValue" + val intValue = 1 + + dataStore.setValue(stringPref, stringValue) + dataStore.setValue(intPref, intValue) + + dataStore.getValueOrNull(stringPref) shouldBe stringValue + dataStore.getValueOrNull(intPref) shouldBe intValue + + dataStore.clear() + + dataStore.getValueOrNull(stringPref) shouldBe null + dataStore.getValueOrNull(intPref) shouldBe null + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt index 075cb827876..55d474287db 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/encryptionmigration/EncryptedPreferencesMigrationTest.kt @@ -16,7 +16,7 @@ import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.verify -import org.joda.time.Instant +import java.time.Instant import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -118,8 +118,14 @@ class EncryptedPreferencesMigrationTest : BaseIOTest() { // SubmissionLocalData every { submissionSettings.registrationTokenMigration = any() } just Runs - every { submissionSettings.initialTestResultReceivedAtMigration = Instant.ofEpochMilli(10101010L) } just Runs - every { submissionSettings.devicePairingSuccessfulAtMigration = Instant.ofEpochMilli(10101010L) } just Runs + every { + submissionSettings.initialTestResultReceivedAtMigration = + org.joda.time.Instant.ofEpochMilli(10101010L) + } just Runs + every { + submissionSettings.devicePairingSuccessfulAtMigration = + org.joda.time.Instant.ofEpochMilli(10101010L) + } just Runs every { submissionSettings.isSubmissionSuccessfulMigration = true } just Runs every { submissionSettings.isAllowedToSubmitKeysMigration = true } just Runs diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/reset/ResetTestComponent.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/reset/ResetTestComponent.kt index 78c6d6360e3..4befe64bc03 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/reset/ResetTestComponent.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/reset/ResetTestComponent.kt @@ -5,8 +5,8 @@ import dagger.Module import dagger.Provides import de.rki.coronawarnapp.appconfig.AppConfigModule import de.rki.coronawarnapp.appconfig.AppConfigProvider -import de.rki.coronawarnapp.bugreporting.BugReportingSettings -import de.rki.coronawarnapp.bugreporting.BugReportingSharedModule +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorage +import de.rki.coronawarnapp.bugreporting.debuglog.upload.history.storage.UploadHistoryStorageModule import de.rki.coronawarnapp.ccl.CclModule import de.rki.coronawarnapp.ccl.configuration.storage.CclConfigurationRepository import de.rki.coronawarnapp.ccl.configuration.storage.DownloadedCclConfigurationStorage @@ -84,7 +84,7 @@ import javax.inject.Singleton modules = [ MockProvider::class, AppConfigModule.ResetModule::class, - BugReportingSharedModule.ResetModule::class, + UploadHistoryStorageModule.ResetModule::class, CclModule.ResetModule::class, ContactDiaryStorageModule.ResetModule::class, CoronaTestModule.ResetModule::class, @@ -125,7 +125,7 @@ object MockProvider { fun provideAppConfigProvider(): AppConfigProvider = mockk(relaxed = true) @Provides - fun provideBugReportingSettings(): BugReportingSettings = mockk(relaxed = true) + fun provideUploadHistoryStorage(): UploadHistoryStorage = mockk(relaxed = true) @Provides fun provideCclSettings(): CclSettings = mockk(relaxed = true) diff --git a/Corona-Warn-App/src/testShared/java/testhelpers/preferences/FakeTypedDataStore.kt b/Corona-Warn-App/src/testShared/java/testhelpers/preferences/FakeTypedDataStore.kt new file mode 100644 index 00000000000..92d0d87a08d --- /dev/null +++ b/Corona-Warn-App/src/testShared/java/testhelpers/preferences/FakeTypedDataStore.kt @@ -0,0 +1,32 @@ +package testhelpers.preferences + +import androidx.datastore.core.DataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.updateAndGet +import timber.log.Timber + +internal class FakeTypedDataStore( + private val defaultValue: T, + private val shouldLog: Boolean = false +) : DataStore { + + private val internalFlow = MutableStateFlow(defaultValue) + + override val data: Flow = internalFlow + .onEach { log("Emitting %s", it) } + + override suspend fun updateData(transform: suspend (t: T) -> T): T = internalFlow.updateAndGet { oldData -> + transform(oldData).also { log("Updated data %s -> %s", oldData, it) } + } + + fun reset() { + log("Resetting to defaultValue=%s", defaultValue) + internalFlow.value = defaultValue + } + + private fun log(msg: String, vararg args: Any?) { + if (shouldLog) Timber.v(msg, *args) + } +} diff --git a/gradle.properties b/gradle.properties index 57ccdb24fcc..d1087ad9c85 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,6 +25,6 @@ kapt.include.compile.classpath=false # Versioning, this is used by the app & pipelines to calculate the current versionCode & versionName VERSION_MAJOR=2 -VERSION_MINOR=23 +VERSION_MINOR=24 VERSION_PATCH=0 -VERSION_BUILD=4 +VERSION_BUILD=0