From c8a0cfd05a1a8864d070aeab057e9367369cdf5f Mon Sep 17 00:00:00 2001
From: AlexanderAlferov <64849422+AlexanderAlferov@users.noreply.github.com>
Date: Thu, 10 Dec 2020 19:25:53 +0300
Subject: [PATCH] New Submission Flow: new test result notification
 (EXPOSUREAPP-4134) (#1825)

* Test result available notification added

* Refactored NotificationHelper to injectable class
Adjusted tests and classes

* Use the pending test result fragment as forwarding tool.

* Fix test regression.

Co-authored-by: Ralf Gehrer <ralfgehrer@users.noreply.github.com>
Co-authored-by: Matthias Urhahn <matthias.urhahn@sap.com>
---
 .../coronawarnapp/CoronaWarnApplication.kt    |   3 +-
 .../notification/NotificationConstants.kt     |   1 +
 .../notification/NotificationHelper.kt        |  60 ++++-----
 .../TestResultAvailableNotification.kt        |  48 ++++++++
 .../TestResultNotificationService.kt          |  11 +-
 .../risk/RiskLevelChangeDetector.kt           |   5 +-
 .../coronawarnapp/util/di/AndroidModule.kt    |   4 +
 .../NavDeepLinkBuilderFactory.kt              |  13 ++
 ...gnosisTestResultRetrievalPeriodicWorker.kt |  21 ++--
 .../TestResultAvailableNotificationTest.kt    | 114 ++++++++++++++++++
 .../risk/RiskLevelChangeDetectorTest.kt       |   5 +-
 .../util/worker/WorkerBinderTest.kt           |   8 ++
 12 files changed, 234 insertions(+), 59 deletions(-)
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotification.kt
 create mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/notifications/NavDeepLinkBuilderFactory.kt
 create mode 100644 Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationTest.kt

diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
index 7bdbcd80d..faa23d485 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/CoronaWarnApplication.kt
@@ -48,6 +48,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector {
     @Inject lateinit var configChangeDetector: ConfigChangeDetector
     @Inject lateinit var riskLevelChangeDetector: RiskLevelChangeDetector
     @Inject lateinit var deadmanNotificationScheduler: DeadmanNotificationScheduler
+    @Inject lateinit var notificationHelper: NotificationHelper
     @LogHistoryTree @Inject lateinit var rollingLogHistory: Timber.Tree
 
     override fun onCreate() {
@@ -62,7 +63,7 @@ class CoronaWarnApplication : Application(), HasAndroidInjector {
 
         Timber.v("onCreate(): WorkManager setup done: $workManager")
 
-        NotificationHelper.createNotificationChannel()
+        notificationHelper.createNotificationChannel()
 
         // Enable Conscrypt for TLS1.3 Support below API Level 29
         Security.insertProviderAt(Conscrypt.newProvider(), 1)
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt
index c8e44982d..66a40de28 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationConstants.kt
@@ -19,6 +19,7 @@ object NotificationConstants {
 
     const val NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID = 110
     const val NEW_MESSAGE_TEST_RESULT_NOTIFICATION_ID = 120
+    const val TEST_RESULT_AVAILABLE_NOTIFICATION_ID = 130
 
     /**
      * Notification channel id String.xml path
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt
index d33f520cd..f0f2db46c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/NotificationHelper.kt
@@ -14,14 +14,16 @@ import android.os.Build
 import androidx.core.app.NotificationCompat
 import androidx.core.app.NotificationCompat.PRIORITY_HIGH
 import androidx.core.app.NotificationManagerCompat
+import dagger.Reusable
 import de.rki.coronawarnapp.BuildConfig
-import de.rki.coronawarnapp.CoronaWarnApplication
 import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.notification.NotificationConstants.NOTIFICATION_ID
 import de.rki.coronawarnapp.ui.main.MainActivity
+import de.rki.coronawarnapp.util.di.AppContext
 import org.joda.time.Duration
 import org.joda.time.Instant
 import timber.log.Timber
+import javax.inject.Inject
 
 /**
  * Singleton class for notification handling
@@ -30,23 +32,22 @@ import timber.log.Timber
  *
  * @see NotificationConstants
  */
-object NotificationHelper {
+@Reusable
+class NotificationHelper @Inject constructor(
+    @AppContext private val context: Context
+) {
 
     /**
      * Notification channel id
      *
      * @see NotificationConstants.NOTIFICATION_CHANNEL_ID
      */
-    private val channelId =
-        CoronaWarnApplication.getAppContext()
-            .getString(NotificationConstants.NOTIFICATION_CHANNEL_ID)
+    private val channelId = context.getString(NotificationConstants.NOTIFICATION_CHANNEL_ID)
 
     /**
      * Notification manager
      */
-    private val notificationManager =
-        CoronaWarnApplication.getAppContext()
-            .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+    private val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
 
     /**
      * Notification channel audio attributes
@@ -70,7 +71,7 @@ object NotificationHelper {
     fun createNotificationChannel() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             val channelName =
-                CoronaWarnApplication.getAppContext().getString(NotificationConstants.CHANNEL_NAME)
+                context.getString(NotificationConstants.CHANNEL_NAME)
 
             val notificationRingtone =
                 RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
@@ -79,7 +80,7 @@ object NotificationHelper {
                 NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
 
             channel.description =
-                CoronaWarnApplication.getAppContext()
+                context
                     .getString(NotificationConstants.CHANNEL_DESCRIPTION)
             channel.setSound(notificationRingtone, audioAttributes)
             notificationManager.createNotificationChannel(channel)
@@ -89,13 +90,13 @@ object NotificationHelper {
     fun cancelFutureNotifications(notificationId: Int) {
         val pendingIntent = createPendingIntentToScheduleNotification(notificationId)
         val manager =
-            CoronaWarnApplication.getAppContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
+            context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
         manager.cancel(pendingIntent)
         Timber.v("Canceled future notifications with id: %s", notificationId)
     }
 
     fun cancelCurrentNotification(notificationId: Int) {
-        NotificationManagerCompat.from(CoronaWarnApplication.getAppContext()).cancel(notificationId)
+        NotificationManagerCompat.from(context).cancel(notificationId)
         Timber.v("Canceled notifications with id: %s", notificationId)
     }
 
@@ -106,7 +107,7 @@ object NotificationHelper {
     ) {
         val pendingIntent = createPendingIntentToScheduleNotification(notificationId)
         val manager =
-            CoronaWarnApplication.getAppContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
+            context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
         manager.setInexactRepeating(AlarmManager.RTC, initialTime.millis, interval.millis, pendingIntent)
     }
 
@@ -115,9 +116,9 @@ object NotificationHelper {
         flag: Int = FLAG_CANCEL_CURRENT
     ) =
         PendingIntent.getBroadcast(
-            CoronaWarnApplication.getAppContext(),
+            context,
             notificationId,
-            Intent(CoronaWarnApplication.getAppContext(), NotificationReceiver::class.java).apply {
+            Intent(context, NotificationReceiver::class.java).apply {
                 putExtra(NOTIFICATION_ID, notificationId)
             },
             flag)
@@ -141,7 +142,7 @@ object NotificationHelper {
         expandableLongText: Boolean = false,
         pendingIntent: PendingIntent = createPendingIntentToMainActivity()
     ): Notification? {
-        val builder = NotificationCompat.Builder(CoronaWarnApplication.getAppContext(), channelId)
+        val builder = NotificationCompat.Builder(context, channelId)
             .setSmallIcon(NotificationConstants.NOTIFICATION_SMALL_ICON)
             .setPriority(NotificationCompat.PRIORITY_MAX)
             .setVisibility(visibility)
@@ -182,9 +183,9 @@ object NotificationHelper {
      */
     private fun createPendingIntentToMainActivity() =
         PendingIntent.getActivity(
-            CoronaWarnApplication.getAppContext(),
+            context,
             0,
-            Intent(CoronaWarnApplication.getAppContext(), MainActivity::class.java),
+            Intent(context, MainActivity::class.java),
             0
         )
 
@@ -199,7 +200,7 @@ object NotificationHelper {
      * @param pendingIntent: PendingIntent
      */
     fun sendNotification(
-        title: String = CoronaWarnApplication.getAppContext().getString(R.string.notification_name),
+        title: String = context.getString(R.string.notification_name),
         content: String,
         notificationId: NotificationId,
         expandableLongText: Boolean = false,
@@ -208,30 +209,11 @@ object NotificationHelper {
         Timber.d("Sending notification with id: %s | title: %s | content: %s", notificationId, title, content)
         val notification =
             buildNotification(title, content, PRIORITY_HIGH, expandableLongText, pendingIntent) ?: return
-        with(NotificationManagerCompat.from(CoronaWarnApplication.getAppContext())) {
+        with(NotificationManagerCompat.from(context)) {
             notify(notificationId, notification)
         }
     }
 
-    /**
-     * Send notification
-     * Build and send notification with content and visibility.
-     * Notification is only sent if app is not in foreground.
-     *
-     * @param content: String
-     * @param notificationId: NotificationId
-     */
-    fun sendNotificationIfAppIsNotInForeground(content: String, notificationId: NotificationId) {
-        if (!CoronaWarnApplication.isAppInForeground) {
-            sendNotification(
-                content = content,
-                notificationId = notificationId,
-                expandableLongText = true)
-        } else {
-            Timber.d("App is in foreground - not sending the notification with id: %s", notificationId)
-        }
-    }
-
     /**
      * Log notification build
      * Log success or failure of creating new notification
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotification.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotification.kt
new file mode 100644
index 000000000..9b99459d2
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultAvailableNotification.kt
@@ -0,0 +1,48 @@
+package de.rki.coronawarnapp.notification
+
+import android.content.Context
+import androidx.navigation.NavDeepLinkBuilder
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.ui.main.MainActivity
+import de.rki.coronawarnapp.util.ForegroundState
+import de.rki.coronawarnapp.util.di.AppContext
+import de.rki.coronawarnapp.util.formatter.TestResult
+import kotlinx.coroutines.flow.first
+import javax.inject.Inject
+import javax.inject.Provider
+
+class TestResultAvailableNotification @Inject constructor(
+    @AppContext private val context: Context,
+    private val foregroundState: ForegroundState,
+    private val navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>,
+    private val notificationHelper: NotificationHelper
+) {
+
+    suspend fun showTestResultNotification(testResult: TestResult) {
+        if (foregroundState.isInForeground.first()) return
+
+        val pendingIntent = navDeepLinkBuilderProvider.get().apply {
+            setGraph(R.navigation.nav_graph)
+            setComponentName(MainActivity::class.java)
+            setDestination(getNotificationDestination(testResult))
+        }.createPendingIntent()
+
+        notificationHelper.sendNotification(
+            title = context.getString(R.string.notification_headline_test_result_ready),
+            content = context.getString(R.string.notification_body_test_result_ready),
+            notificationId = NotificationConstants.TEST_RESULT_AVAILABLE_NOTIFICATION_ID,
+            pendingIntent = pendingIntent
+        )
+    }
+
+    /**
+     * The pending result fragment will forward to the correct screen
+     * Because we can't save the test result at the moment (legal),
+     * it needs to be reloaded each time.
+     * If we navigate directly to the positive/negative result screen,
+     * then we also need to add explicit test loading logic there.
+     * By letting the forwarding happen via the PendingResultFragment,
+     * we have a common location to retrieve the test result.
+     */
+    fun getNotificationDestination(testResult: TestResult): Int = R.id.submissionTestResultPendingFragment
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultNotificationService.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultNotificationService.kt
index ecfeb8f79..4bffacf4c 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultNotificationService.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/notification/TestResultNotificationService.kt
@@ -16,14 +16,15 @@ import javax.inject.Inject
 
 class TestResultNotificationService @Inject constructor(
     @AppContext private val context: Context,
-    private val timeStamper: TimeStamper
+    private val timeStamper: TimeStamper,
+    private val notificationHelper: NotificationHelper
 ) {
 
     fun schedulePositiveTestResultReminder() {
         if (LocalData.numberOfRemainingPositiveTestResultReminders < 0) {
             Timber.v("Schedule positive test result notification")
             LocalData.numberOfRemainingPositiveTestResultReminders = POSITIVE_RESULT_NOTIFICATION_TOTAL_COUNT
-            NotificationHelper.scheduleRepeatingNotification(
+            notificationHelper.scheduleRepeatingNotification(
                 timeStamper.nowUTC.plus(POSITIVE_RESULT_NOTIFICATION_INITIAL_OFFSET),
                 POSITIVE_RESULT_NOTIFICATION_INTERVAL,
                 POSITIVE_RESULT_NOTIFICATION_ID
@@ -42,19 +43,19 @@ class TestResultNotificationService @Inject constructor(
                 .setDestination(R.id.submissionTestResultAvailableFragment)
                 .createPendingIntent()
 
-            NotificationHelper.sendNotification(
+            notificationHelper.sendNotification(
                 title = context.getString(R.string.notification_headline_share_positive_result),
                 content = context.getString(R.string.notification_body_share_positive_result),
                 notificationId = notificationId,
                 pendingIntent = pendingIntent
             )
         } else {
-            NotificationHelper.cancelFutureNotifications(notificationId)
+            notificationHelper.cancelFutureNotifications(notificationId)
         }
     }
 
     fun cancelPositiveTestResultNotification() {
-        NotificationHelper.cancelFutureNotifications(POSITIVE_RESULT_NOTIFICATION_ID)
+        notificationHelper.cancelFutureNotifications(POSITIVE_RESULT_NOTIFICATION_ID)
         Timber.v("Future positive test result notifications have been canceled")
     }
 
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
index e2d403a1d..5aff973f1 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetector.kt
@@ -27,7 +27,8 @@ class RiskLevelChangeDetector @Inject constructor(
     private val riskLevelStorage: RiskLevelStorage,
     private val riskLevelSettings: RiskLevelSettings,
     private val notificationManagerCompat: NotificationManagerCompat,
-    private val foregroundState: ForegroundState
+    private val foregroundState: ForegroundState,
+    private val notificationHelper: NotificationHelper
 ) {
 
     fun launch() {
@@ -65,7 +66,7 @@ class RiskLevelChangeDetector @Inject constructor(
             Timber.d("Notification Permission = ${notificationManagerCompat.areNotificationsEnabled()}")
 
             if (!foregroundState.isInForeground.first()) {
-                NotificationHelper.sendNotification(
+                notificationHelper.sendNotification(
                     content = context.getString(R.string.notification_body),
                     notificationId = NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
                 )
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
index c8a9be0e7..a5d549bff 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/di/AndroidModule.kt
@@ -5,6 +5,7 @@ import android.bluetooth.BluetoothAdapter
 import android.content.Context
 import android.content.SharedPreferences
 import androidx.core.app.NotificationManagerCompat
+import androidx.navigation.NavDeepLinkBuilder
 import androidx.work.WorkManager
 import dagger.Module
 import dagger.Provides
@@ -46,4 +47,7 @@ class AndroidModule {
     @Provides
     @Singleton
     fun encryptedPreferences(): SharedPreferences = SecurityHelper.globalEncryptedSharedPreferencesInstance
+
+    @Provides
+    fun navDeepLinkBuilder(@AppContext context: Context): NavDeepLinkBuilder = NavDeepLinkBuilder(context)
 }
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/notifications/NavDeepLinkBuilderFactory.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/notifications/NavDeepLinkBuilderFactory.kt
new file mode 100644
index 000000000..91de2b267
--- /dev/null
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/notifications/NavDeepLinkBuilderFactory.kt
@@ -0,0 +1,13 @@
+package de.rki.coronawarnapp.util.notifications
+
+import android.content.Context
+import androidx.navigation.NavDeepLinkBuilder
+import dagger.Reusable
+import javax.inject.Inject
+
+@Reusable
+class NavDeepLinkBuilderFactory @Inject constructor() {
+    fun create(context: Context): NavDeepLinkBuilder {
+        return NavDeepLinkBuilder(context)
+    }
+}
diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
index 1e5a28128..0ced5280b 100644
--- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
+++ b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/worker/DiagnosisTestResultRetrievalPeriodicWorker.kt
@@ -5,12 +5,10 @@ import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
 import com.squareup.inject.assisted.Assisted
 import com.squareup.inject.assisted.AssistedInject
-import de.rki.coronawarnapp.CoronaWarnApplication
-import de.rki.coronawarnapp.R
 import de.rki.coronawarnapp.exception.NoRegistrationTokenSetException
-import de.rki.coronawarnapp.notification.NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID
-import de.rki.coronawarnapp.notification.NotificationConstants.NEW_MESSAGE_TEST_RESULT_NOTIFICATION_ID
+import de.rki.coronawarnapp.notification.NotificationConstants
 import de.rki.coronawarnapp.notification.NotificationHelper
+import de.rki.coronawarnapp.notification.TestResultAvailableNotification
 import de.rki.coronawarnapp.service.submission.SubmissionService
 import de.rki.coronawarnapp.storage.LocalData
 import de.rki.coronawarnapp.util.TimeAndDateExtensions
@@ -27,7 +25,9 @@ import timber.log.Timber
 class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
     @Assisted val context: Context,
     @Assisted workerParams: WorkerParameters,
-    private val submissionService: SubmissionService
+    private val submissionService: SubmissionService,
+    private val testResultAvailableNotification: TestResultAvailableNotification,
+    private val notificationHelper: NotificationHelper
 ) : CoroutineWorker(context, workerParams) {
 
     /**
@@ -84,7 +84,7 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
      * @see LocalData.initialPollingForTestResultTimeStamp
      * @see TestResult
      */
-    private fun initiateNotification(testResult: TestResult) {
+    private suspend fun initiateNotification(testResult: TestResult) {
         if (LocalData.isTestResultNotificationSent() || LocalData.submissionWasSuccessful()) {
             Timber.tag(TAG).d("$id: Notification already sent or there was a successful submission")
             return
@@ -93,11 +93,10 @@ class DiagnosisTestResultRetrievalPeriodicWorker @AssistedInject constructor(
         if (testResult == TestResult.NEGATIVE || testResult == TestResult.POSITIVE ||
             testResult == TestResult.INVALID
         ) {
-            NotificationHelper.sendNotificationIfAppIsNotInForeground(
-                CoronaWarnApplication.getAppContext().getString(R.string.notification_body),
-                NEW_MESSAGE_TEST_RESULT_NOTIFICATION_ID
-            )
-            NotificationHelper.cancelCurrentNotification(NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID)
+            testResultAvailableNotification.showTestResultNotification(testResult)
+
+            notificationHelper.cancelCurrentNotification(
+                NotificationConstants.NEW_MESSAGE_RISK_LEVEL_SCORE_NOTIFICATION_ID)
 
             Timber.tag(TAG).d("$id: Test Result available - notification issued & risk level notification canceled")
             LocalData.isTestResultNotificationSent(true)
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationTest.kt
new file mode 100644
index 000000000..9355950b4
--- /dev/null
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/notification/TestResultAvailableNotificationTest.kt
@@ -0,0 +1,114 @@
+package de.rki.coronawarnapp.notification
+
+import android.app.NotificationManager
+import android.app.PendingIntent
+import android.content.Context
+import androidx.navigation.NavDeepLinkBuilder
+import de.rki.coronawarnapp.CoronaWarnApplication
+import de.rki.coronawarnapp.R
+import de.rki.coronawarnapp.util.ForegroundState
+import de.rki.coronawarnapp.util.formatter.TestResult
+import io.kotest.matchers.shouldBe
+import io.mockk.MockKAnnotations
+import io.mockk.Runs
+import io.mockk.clearAllMocks
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.just
+import io.mockk.mockkObject
+import io.mockk.verify
+import io.mockk.verifyOrder
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.test.runBlockingTest
+import org.junit.jupiter.api.AfterEach
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+import javax.inject.Provider
+
+class TestResultAvailableNotificationTest {
+
+    @MockK(relaxed = true) lateinit var context: Context
+    @MockK lateinit var foregroundState: ForegroundState
+    @MockK(relaxed = true) lateinit var navDeepLinkBuilder: NavDeepLinkBuilder
+    @MockK lateinit var pendingIntent: PendingIntent
+    @MockK lateinit var navDeepLinkBuilderProvider: Provider<NavDeepLinkBuilder>
+    @MockK lateinit var notificationManager: NotificationManager
+    @MockK lateinit var notificationHelper: NotificationHelper
+
+    @BeforeEach
+    fun setUp() {
+        MockKAnnotations.init(this)
+
+        mockkObject(CoronaWarnApplication)
+
+        every { CoronaWarnApplication.getAppContext() } returns context
+        every { context.getString(NotificationConstants.NOTIFICATION_CHANNEL_ID) } returns "notification_channel_id"
+        every { context.getSystemService(Context.NOTIFICATION_SERVICE) } returns notificationManager
+        every { navDeepLinkBuilderProvider.get() } returns navDeepLinkBuilder
+        every { navDeepLinkBuilder.createPendingIntent() } returns pendingIntent
+    }
+
+    @AfterEach
+    fun teardown() {
+        clearAllMocks()
+    }
+
+    fun createInstance() = TestResultAvailableNotification(
+        context = context,
+        foregroundState = foregroundState,
+        navDeepLinkBuilderProvider = navDeepLinkBuilderProvider,
+        notificationHelper = notificationHelper
+    )
+
+    @Test
+    fun `check destination`() {
+        val negative = createInstance().getNotificationDestination(TestResult.NEGATIVE)
+        negative shouldBe (R.id.submissionTestResultPendingFragment)
+
+        val invalid = createInstance().getNotificationDestination(TestResult.INVALID)
+        invalid shouldBe (R.id.submissionTestResultPendingFragment)
+
+        val positive = createInstance().getNotificationDestination(TestResult.POSITIVE)
+        positive shouldBe (R.id.submissionTestResultPendingFragment)
+    }
+
+    @Test
+    fun `test notification in foreground`() = runBlockingTest {
+        coEvery { foregroundState.isInForeground } returns flow { emit(true) }
+
+        createInstance().showTestResultNotification(TestResult.POSITIVE)
+
+        verify(exactly = 0) { navDeepLinkBuilderProvider.get() }
+    }
+
+    @Test
+    fun `test notification in background`() = runBlockingTest {
+        coEvery { foregroundState.isInForeground } returns flow { emit(false) }
+        every {
+            notificationHelper.sendNotification(
+                title = any(),
+                content = any(),
+                notificationId = any(),
+                pendingIntent = any()
+            )
+        } just Runs
+
+        val instance = createInstance()
+
+        instance.showTestResultNotification(TestResult.POSITIVE)
+
+        verifyOrder {
+            navDeepLinkBuilderProvider.get()
+            instance.getNotificationDestination(TestResult.POSITIVE)
+            context.getString(R.string.notification_headline_test_result_ready)
+            context.getString(R.string.notification_body_test_result_ready)
+            notificationHelper.sendNotification(
+                title = any(),
+                content = any(),
+                notificationId = any(),
+                pendingIntent = any()
+            )
+        }
+    }
+}
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
index f8b920807..f9e7e25d8 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/risk/RiskLevelChangeDetectorTest.kt
@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.risk
 import android.content.Context
 import androidx.core.app.NotificationManagerCompat
 import com.google.android.gms.nearby.exposurenotification.ExposureWindow
+import de.rki.coronawarnapp.notification.NotificationHelper
 import de.rki.coronawarnapp.risk.RiskState.CALCULATION_FAILED
 import de.rki.coronawarnapp.risk.RiskState.INCREASED_RISK
 import de.rki.coronawarnapp.risk.RiskState.LOW_RISK
@@ -38,6 +39,7 @@ class RiskLevelChangeDetectorTest : BaseTest() {
     @MockK lateinit var notificationManagerCompat: NotificationManagerCompat
     @MockK lateinit var foregroundState: ForegroundState
     @MockK lateinit var riskLevelSettings: RiskLevelSettings
+    @MockK lateinit var notificationHelper: NotificationHelper
 
     @BeforeEach
     fun setup() {
@@ -77,7 +79,8 @@ class RiskLevelChangeDetectorTest : BaseTest() {
         riskLevelStorage = riskLevelStorage,
         notificationManagerCompat = notificationManagerCompat,
         foregroundState = foregroundState,
-        riskLevelSettings = riskLevelSettings
+        riskLevelSettings = riskLevelSettings,
+        notificationHelper = notificationHelper
     )
 
     @Test
diff --git a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
index 385d00586..3dfbb16c0 100644
--- a/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
+++ b/Corona-Warn-App/src/test/java/de/rki/coronawarnapp/util/worker/WorkerBinderTest.kt
@@ -7,6 +7,8 @@ import dagger.Provides
 import de.rki.coronawarnapp.deadman.DeadmanNotificationScheduler
 import de.rki.coronawarnapp.deadman.DeadmanNotificationSender
 import de.rki.coronawarnapp.nearby.ENFClient
+import de.rki.coronawarnapp.notification.NotificationHelper
+import de.rki.coronawarnapp.notification.TestResultAvailableNotification
 import de.rki.coronawarnapp.playbook.Playbook
 import de.rki.coronawarnapp.risk.storage.RiskLevelStorage
 import de.rki.coronawarnapp.task.TaskController
@@ -96,4 +98,10 @@ class MockProvider {
 
     @Provides
     fun exposureSummaryRepository(): RiskLevelStorage = mockk()
+
+    @Provides
+    fun testResultAvailableNotification(): TestResultAvailableNotification = mockk()
+
+    @Provides
+    fun notificationHelper(): NotificationHelper = mockk()
 }
-- 
GitLab