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 99c18ab32dd265ee969d06385ec1af369b7caa22..cfb6696c5fc9f3fd965e79a27e7f01d311fe0420 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 @@ -3,8 +3,15 @@ package de.rki.coronawarnapp.bugreporting import dagger.Module import dagger.Provides import de.rki.coronawarnapp.bugreporting.censors.BugCensor +import de.rki.coronawarnapp.bugreporting.censors.DiaryLocationCensor +import de.rki.coronawarnapp.bugreporting.censors.DiaryPersonCensor +import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor import de.rki.coronawarnapp.bugreporting.censors.RegistrationTokenCensor import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger +import de.rki.coronawarnapp.bugreporting.debuglog.DebugLoggerScope +import de.rki.coronawarnapp.bugreporting.debuglog.DebuggerScope +import kotlinx.coroutines.CoroutineScope +import timber.log.Timber import javax.inject.Singleton @Module @@ -14,9 +21,24 @@ class BugReportingSharedModule { @Provides fun debugLogger() = DebugLogger + @Singleton + @DebuggerScope + @Provides + fun scope(): CoroutineScope = DebugLoggerScope + @Singleton @Provides fun censors( - registrationTokenCensor: RegistrationTokenCensor - ): List<BugCensor> = listOf(registrationTokenCensor) + registrationTokenCensor: RegistrationTokenCensor, + diaryPersonCensor: DiaryPersonCensor, + diaryLocationCensor: DiaryLocationCensor, + qrCodeCensor: QRCodeCensor + ): List<BugCensor> = listOf( + registrationTokenCensor, + diaryPersonCensor, + diaryLocationCensor, + qrCodeCensor + ).also { + Timber.d("Loaded BugCensors: %s", it) + } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt index e9c2c69eaecdf5f9b85ad5e321db0af8cc6bcb33..1540f5b4d283703db2e55574aec1fdcaf7942e69 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/BugCensor.kt @@ -7,5 +7,5 @@ interface BugCensor { /** * If there is something to censor a new log line is returned, otherwise returns null */ - fun checkLog(entry: LogLine): LogLine? + suspend fun checkLog(entry: LogLine): LogLine? } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt new file mode 100644 index 0000000000000000000000000000000000000000..6a30f19455a285626af3ae95ad45c2ca044f3f00 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensor.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.debuglog.DebuggerScope +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.util.CWADebug +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@Reusable +class DiaryLocationCensor @Inject constructor( + @DebuggerScope debugScope: CoroutineScope, + diary: ContactDiaryRepository +) : BugCensor { + + private val locations by lazy { + diary.locations.stateIn( + scope = debugScope, + started = SharingStarted.Lazily, + initialValue = null + ).filterNotNull() + } + + override suspend fun checkLog(entry: LogLine): LogLine? { + val locationsNow = locations.first() + + if (locationsNow.isEmpty()) return null + + var newMessage = locationsNow.fold(entry.message) { oldMsg, location -> + oldMsg.replace(location.locationName, "Location#${location.locationId}") + } + + if (CWADebug.isDeviceForTestersBuild) { + newMessage = entry.message + } + + return entry.copy(message = newMessage) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt new file mode 100644 index 0000000000000000000000000000000000000000..f2406f14ec04ace933a352628fbed51aed8425b5 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensor.kt @@ -0,0 +1,44 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.debuglog.DebuggerScope +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.util.CWADebug +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn +import javax.inject.Inject + +@Reusable +class DiaryPersonCensor @Inject constructor( + @DebuggerScope debugScope: CoroutineScope, + diary: ContactDiaryRepository +) : BugCensor { + + private val persons by lazy { + diary.people.stateIn( + scope = debugScope, + started = SharingStarted.Lazily, + initialValue = null + ).filterNotNull() + } + + override suspend fun checkLog(entry: LogLine): LogLine? { + val personsNow = persons.first() + + if (personsNow.isEmpty()) return null + + var newMessage = personsNow.fold(entry.message) { oldMsg, person -> + oldMsg.replace(person.fullName, "Person#${person.personId}") + } + + if (CWADebug.isDeviceForTestersBuild) { + newMessage = entry.message + } + + return entry.copy(message = newMessage) + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt new file mode 100644 index 0000000000000000000000000000000000000000..208ac88117faf57f908ffa012132c8111bbaab80 --- /dev/null +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensor.kt @@ -0,0 +1,29 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import dagger.Reusable +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.util.CWADebug +import javax.inject.Inject + +@Reusable +class QRCodeCensor @Inject constructor() : BugCensor { + + override suspend fun checkLog(entry: LogLine): LogLine? { + + val guid = lastGUID ?: return null + if (!entry.message.contains(guid)) return null + + var newMessage = entry.message.replace(guid, PLACEHOLDER + guid.takeLast(4)) + + if (CWADebug.isDeviceForTestersBuild) { + newMessage = entry.message + } + + return entry.copy(message = newMessage) + } + + companion object { + var lastGUID: String? = null + private const val PLACEHOLDER = "########-####-####-####-########" + } +} diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt index 5832e2f6f7965c1c7cc7f0427e23210201d904b6..590df2a5aebfe0b81ac7292a61f37cae74bab5ea 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensor.kt @@ -5,19 +5,23 @@ import de.rki.coronawarnapp.bugreporting.debuglog.LogLine import de.rki.coronawarnapp.storage.LocalData import de.rki.coronawarnapp.util.CWADebug import javax.inject.Inject -import kotlin.math.min @Reusable class RegistrationTokenCensor @Inject constructor() : BugCensor { - override fun checkLog(entry: LogLine): LogLine? { + override suspend fun checkLog(entry: LogLine): LogLine? { val token = LocalData.registrationToken() ?: return null if (!entry.message.contains(token)) return null - val replacement = if (CWADebug.isDeviceForTestersBuild) { - token - } else { - token.substring(0, min(4, token.length)) + "###-####-####-####-############" + var newMessage = entry.message.replace(token, PLACEHOLDER + token.takeLast(4)) + + if (CWADebug.isDeviceForTestersBuild) { + newMessage = entry.message } - return entry.copy(message = entry.message.replace(token, replacement)) + + return entry.copy(message = newMessage) + } + + companion object { + private const val PLACEHOLDER = "########-####-####-####-########" } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt index 2f4e3c06558e0478ac895d7bef9c10447683edc7..e6e33aeeb1e308b9af1237b3d52575b7560cfeec 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLogger.kt @@ -8,6 +8,7 @@ import de.rki.coronawarnapp.util.CWADebug import de.rki.coronawarnapp.util.di.ApplicationComponent import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -107,8 +108,14 @@ object DebugLogger : DebugLoggerBase() { while (!isDaggerReady) { yield() } - val censoredLine = bugCensors.get().mapNotNull { it.checkLog(rawLine) }.firstOrNull() - appendLogLine(censoredLine ?: rawLine) + launch { + // Censor data sources need a moment to know what to censor + delay(1000) + val censoredLine = bugCensors.get().fold(rawLine) { prev, censor -> + censor.checkLog(prev) ?: prev + } + appendLogLine(censoredLine) + } } } catch (e: CancellationException) { Timber.tag(TAG).i("Logging was canceled.") diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt index 4943b1f113c5861b9a3b46247acc138b24acf92e..6b48f8632877b7999c9e56a5fa2ce704788d104a 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/bugreporting/debuglog/DebugLoggerScope.kt @@ -6,10 +6,8 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.Executors import javax.inject.Qualifier -import javax.inject.Singleton import kotlin.coroutines.CoroutineContext -@Singleton object DebugLoggerScope : CoroutineScope { val dispatcher = Executors.newSingleThreadExecutor( NamedThreadFactory("DebugLogger") @@ -20,4 +18,4 @@ object DebugLoggerScope : CoroutineScope { @Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) -annotation class AppScope +annotation class DebuggerScope diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt index 198e37eaeb01c901af3b5ec9372c52a57ec48dad..419cdeff8a361e81fe05ad2f9eb839c5e5145471 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/information/InformationFragment.kt @@ -58,7 +58,7 @@ class InformationFragment : Fragment(R.layout.fragment_information), AutoInject setAccessibilityDelegate() // TODO Hidden until further clarification regarding release schedule is available - binding.informationDebuglog.mainRow.isGone = !CWADebug.isDeviceForTestersBuild + binding.informationDebuglog.rootLayout.isGone = !CWADebug.isDeviceForTestersBuild } override fun onResume() { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt index 8ce0bd3342bc2ea9359326b5ea45a97aa07ea962..37b4f7c72f38c01c72b7f87a900d0ea47683f099 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModel.kt @@ -2,6 +2,7 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan import androidx.lifecycle.MutableLiveData import com.squareup.inject.assisted.AssistedInject +import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor import de.rki.coronawarnapp.exception.ExceptionCategory import de.rki.coronawarnapp.exception.TransactionException import de.rki.coronawarnapp.exception.http.CwaWebException @@ -30,6 +31,7 @@ class SubmissionQRCodeScanViewModel @AssistedInject constructor( fun validateTestGUID(rawResult: String) { val scanResult = QRScanResult(rawResult) if (scanResult.isValid) { + QRCodeCensor.lastGUID = scanResult.guid scanStatusValue.postValue(ScanStatus.SUCCESS) doDeviceRegistration(scanResult) } else { diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt index a9887e63f59b9bb5d4a46bc13a98d66633147442..2b7abc116354400d187f0d10533c82848a2d4fc2 100644 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt +++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/debug/UncaughtExceptionLogger.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.util.debug +import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger import timber.log.Timber class UncaughtExceptionLogger( @@ -12,6 +13,10 @@ class UncaughtExceptionLogger( override fun uncaughtException(thread: Thread, error: Throwable) { Timber.tag(thread.name).e(error, "Uncaught exception!") + if (DebugLogger.isLogging) { + // Make sure this crash is written before killing the app. + Thread.sleep(1500) + } wrappedHandler?.uncaughtException(thread, error) } diff --git a/Corona-Warn-App/src/main/res/layout/include_row.xml b/Corona-Warn-App/src/main/res/layout/include_row.xml index 1f5997334910cc2f9d2496bc5c6b26dca5697743..aa8e130471b2022039d3ff6cae2a001f174d7ae7 100644 --- a/Corona-Warn-App/src/main/res/layout/include_row.xml +++ b/Corona-Warn-App/src/main/res/layout/include_row.xml @@ -27,6 +27,7 @@ <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:id="@+id/root_layout" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..991635564d0a71584e7d537bf9d00bc1ab022303 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryLocationCensorTest.kt @@ -0,0 +1,86 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryLocation +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.util.CWADebug +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class DiaryLocationCensorTest : BaseTest() { + + @MockK lateinit var diaryRepo: ContactDiaryRepository + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns false + } + + @AfterEach + fun teardown() { + QRCodeCensor.lastGUID = null + clearAllMocks() + } + + private fun createInstance(scope: CoroutineScope) = DiaryLocationCensor( + debugScope = scope, + diary = diaryRepo + ) + + private fun mockLocation(id: Long, name: String) = mockk<ContactDiaryLocation>().apply { + every { locationId } returns id + every { locationName } returns name + } + + @Test + fun `censoring replaces the logline message`() = runBlockingTest { + every { diaryRepo.locations } returns flowOf( + listOf(mockLocation(1, "Berlin"), mockLocation(2, "Munich"), mockLocation(3, "Aachen")) + ) + val instance = createInstance(this) + val censorMe = LogLine( + timestamp = 1, + priority = 3, + message = "Munich is nice, but Aachen is nice too.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = "Location#2 is nice, but Location#3 is nice too." + ) + + every { CWADebug.isDeviceForTestersBuild } returns true + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = "Munich is nice, but Aachen is nice too." + ) + } + + @Test + fun `censoring returns null if there are no locations no match`() = runBlockingTest { + every { diaryRepo.locations } returns flowOf(emptyList()) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "Can't visit many cities during lockdown...", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..fe731ed3e4c4ef4cd59280abc2fde2aedeae155f --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/DiaryPersonCensorTest.kt @@ -0,0 +1,86 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.contactdiary.model.ContactDiaryPerson +import de.rki.coronawarnapp.contactdiary.storage.repo.ContactDiaryRepository +import de.rki.coronawarnapp.util.CWADebug +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.mockkObject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class DiaryPersonCensorTest : BaseTest() { + + @MockK lateinit var diaryRepo: ContactDiaryRepository + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns false + } + + @AfterEach + fun teardown() { + QRCodeCensor.lastGUID = null + clearAllMocks() + } + + private fun createInstance(scope: CoroutineScope) = DiaryPersonCensor( + debugScope = scope, + diary = diaryRepo + ) + + private fun mockPerson(id: Long, name: String) = mockk<ContactDiaryPerson>().apply { + every { personId } returns id + every { fullName } returns name + } + + @Test + fun `censoring replaces the logline message`() = runBlockingTest { + every { diaryRepo.people } returns flowOf( + listOf(mockPerson(1, "Luka"), mockPerson(2, "Ralf"), mockPerson(3, "Matthias")) + ) + val instance = createInstance(this) + val censorMe = LogLine( + timestamp = 1, + priority = 3, + message = "Ralf needs more coffee, but Matthias has had enough for today.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = "Person#2 needs more coffee, but Person#3 has had enough for today." + ) + + every { CWADebug.isDeviceForTestersBuild } returns true + instance.checkLog(censorMe) shouldBe censorMe.copy( + message = "Ralf needs more coffee, but Matthias has had enough for today." + ) + } + + @Test + fun `censoring returns null if there are no persons no match`() = runBlockingTest { + every { diaryRepo.people } returns flowOf(emptyList()) + val instance = createInstance(this) + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "May 2021 be better than 2020.", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..0fc7a543d5e765c5c3602fb9a8164fe5f0098089 --- /dev/null +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/QRCodeCensorTest.kt @@ -0,0 +1,84 @@ +package de.rki.coronawarnapp.bugreporting.censors + +import de.rki.coronawarnapp.bugreporting.debuglog.LogLine +import de.rki.coronawarnapp.util.CWADebug +import io.kotest.matchers.shouldBe +import io.mockk.MockKAnnotations +import io.mockk.clearAllMocks +import io.mockk.every +import io.mockk.mockkObject +import kotlinx.coroutines.test.runBlockingTest +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import testhelpers.BaseTest + +class QRCodeCensorTest : BaseTest() { + + private val testGUID = "63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" + + @BeforeEach + fun setup() { + MockKAnnotations.init(this) + + mockkObject(CWADebug) + every { CWADebug.isDeviceForTestersBuild } returns false + } + + @AfterEach + fun teardown() { + QRCodeCensor.lastGUID = null + clearAllMocks() + } + + private fun createInstance() = QRCodeCensor() + + @Test + fun `censoring replaces the logline message`() = runBlockingTest { + QRCodeCensor.lastGUID = testGUID + val instance = createInstance() + val censored = LogLine( + timestamp = 1, + priority = 3, + message = "I'm a shy qrcode: $testGUID", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(censored) shouldBe censored.copy( + message = "I'm a shy qrcode: ########-####-####-####-########3a2f" + ) + + every { CWADebug.isDeviceForTestersBuild } returns true + instance.checkLog(censored) shouldBe censored.copy( + message = "I'm a shy qrcode: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" + ) + } + + @Test + fun `censoring returns null if there is no match`() = runBlockingTest { + QRCodeCensor.lastGUID = testGUID.replace("f", "a") + val instance = createInstance() + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "I'm a shy qrcode: $testGUID", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } + + @Test + fun `censoring aborts if no qrcode was set`() = runBlockingTest { + QRCodeCensor.lastGUID = null + val instance = createInstance() + val notCensored = LogLine( + timestamp = 1, + priority = 3, + message = "I'm a shy qrcode: $testGUID", + tag = "I'm a tag", + throwable = null + ) + instance.checkLog(notCensored) shouldBe null + } +} diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt index 8c13580efb27805aea69afab697fdb953436cff1..89ae8cd1e09b62ee503265e982c0689567de44d7 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/bugreporting/censors/RegistrationTokenCensorTest.kt @@ -9,6 +9,7 @@ import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockkObject import io.mockk.verify +import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -37,7 +38,7 @@ class RegistrationTokenCensorTest : BaseTest() { private fun createInstance() = RegistrationTokenCensor() @Test - fun `censoring replaces the logline message`() { + fun `censoring replaces the logline message`() = runBlockingTest { val instance = createInstance() val filterMe = LogLine( timestamp = 1, @@ -47,19 +48,25 @@ class RegistrationTokenCensorTest : BaseTest() { throwable = null ) instance.checkLog(filterMe) shouldBe filterMe.copy( - message = "I'm a shy registration token: 63b4###-####-####-####-############" + message = "I'm a shy registration token: ########-####-####-####-########3a2f" + ) + + every { CWADebug.isDeviceForTestersBuild } returns true + instance.checkLog(filterMe) shouldBe filterMe.copy( + message = "I'm a shy registration token: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" ) verify { LocalData.registrationToken() } } @Test - fun `censoring returns null if thereis no match`() { + fun `censoring returns null if there is no token`() = runBlockingTest { + every { LocalData.registrationToken() } returns null val instance = createInstance() val filterMeNot = LogLine( timestamp = 1, priority = 3, - message = "I'm not a registration token ;)", + message = "I'm a shy registration token: $testToken", tag = "I'm a tag", throwable = null ) @@ -67,20 +74,15 @@ class RegistrationTokenCensorTest : BaseTest() { } @Test - fun `token is not censored on tester builds`() { - every { CWADebug.isDeviceForTestersBuild } returns true + fun `censoring returns null if there is no match`() = runBlockingTest { val instance = createInstance() - val filterMe = LogLine( + val filterMeNot = LogLine( timestamp = 1, priority = 3, - message = "I'm a shy registration token: $testToken", + message = "I'm not a registration token ;)", tag = "I'm a tag", throwable = null ) - instance.checkLog(filterMe) shouldBe filterMe.copy( - message = "I'm a shy registration token: 63b4d3ff-e0de-4bd4-90c1-17c2bb683a2f" - ) - - verify { LocalData.registrationToken() } + instance.checkLog(filterMeNot) shouldBe null } } diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt index b4136caab09231e84b44b7dcac4c68f042fd0aef..9df500c2100d040098acc1721907668bd8d9677a 100644 --- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt +++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/ui/submission/qrcode/scan/SubmissionQRCodeScanViewModelTest.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.ui.submission.qrcode.scan +import de.rki.coronawarnapp.bugreporting.censors.QRCodeCensor import de.rki.coronawarnapp.playbook.BackgroundNoise import de.rki.coronawarnapp.submission.SubmissionRepository import de.rki.coronawarnapp.ui.submission.ScanStatus @@ -40,10 +41,13 @@ class SubmissionQRCodeScanViewModelTest : BaseTest() { viewModel.scanStatusValue.value shouldBe ScanStatus.STARTED + QRCodeCensor.lastGUID = null + // valid guid val guid = "123456-12345678-1234-4DA7-B166-B86D85475064" viewModel.validateTestGUID("https://localhost/?$guid") viewModel.scanStatusValue.let { Assert.assertEquals(ScanStatus.SUCCESS, it.value) } + QRCodeCensor.lastGUID = guid // invalid guid viewModel.validateTestGUID("https://no-guid-here")