* test(#291): add coverage for 3 critical untested components - NavigationController dedup guard (line 45): 14 pure unit tests for navigateTo, resetTo, goBack, primary-screen clearing, and null guards. - NotificationReplyReceiver: 11 MockK-based tests covering the full WS+Room+notification flow: valid reply sends WS message, persists to Room with correct entity shape, shows replied confirmation notification, finishes PendingResult, and gracefully handles null/blank inputs and Room failures. - ChatNotificationService isAppInForeground flag: 5 tests verifying companion object toggle, volatile modifier (thread safety), default false state, and idempotent calls. - Fix: add @Volatile to isAppInForeground (was a plain var read from background coroutine and written from UI thread) Closes #291 * fix(#291): fix MockK type errors in test compilation * fix(#291): fix test assertions and goAsync mock for CI * fix(#291): wrap final BroadcastReceiver.goAsync() with open Kotlin wrapper * chore: trigger CI on latest fix commit * fix(#291): make NotificationReplyReceiver open so tests can subclass it * fix(#291): extract buildReplyNotification() to avoid 'not mocked' framework error NotificationCompat.Builder.build() internally calls android.app.Notification.Builder methods (e.g. setWhen) that throw RuntimeException with 'Method setWhen in android.app.Notification.Builder not mocked' in unit tests — the standard Android unit test environment does not provide real framework implementations. Extracting notification creation into an open method (same pattern as goAsyncCompat()) lets tests override it to return a mock Notification, avoiding the framework call entirely. * style: fix ktlint blank-line-before-declaration in test --------- Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
1 parent
bbe3b00ccb
commit
c44f4c3068
5 files changed
+632
-11
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package com.m57.hermescontrol.notification
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
@@ -10,12 +11,34 @@ import com.m57.hermescontrol.R
|
||||
import com.m57.hermescontrol.data.ws.HermesWsClient
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
open class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
companion object {
|
||||
const val KEY_TEXT_REPLY = "key_text_reply"
|
||||
const val EXTRA_SESSION_ID = "extra_session_id"
|
||||
}
|
||||
|
||||
/**
|
||||
* Test-friendly wrapper for [BroadcastReceiver.goAsync] which is `final`
|
||||
* (Java) and cannot be mocked or overridden directly. Tests override this
|
||||
* via anonymous subclass to inject a fake [PendingResult].
|
||||
*/
|
||||
internal open fun goAsyncCompat(): BroadcastReceiver.PendingResult = goAsync()
|
||||
|
||||
/**
|
||||
* Test-friendly wrapper for notification creation. Override in tests to
|
||||
* avoid [NotificationCompat.Builder.build()] calling Android framework
|
||||
* methods that throw "not mocked" in unit tests.
|
||||
*/
|
||||
internal open fun buildReplyNotification(context: Context): Notification =
|
||||
NotificationCompat
|
||||
.Builder(context, ChatNotificationService.CHAT_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("Hermes")
|
||||
.setContentText("Replied")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
|
||||
override fun onReceive(
|
||||
context: Context,
|
||||
intent: Intent,
|
||||
@@ -27,7 +50,7 @@ class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
if (!replyText.isNullOrBlank() && !sessionId.isNullOrBlank()) {
|
||||
HermesWsClient.sendMessage(sessionId, replyText)
|
||||
|
||||
val pendingResult = goAsync()
|
||||
val pendingResult = goAsyncCompat()
|
||||
kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.IO).launch {
|
||||
try {
|
||||
val db =
|
||||
@@ -53,15 +76,7 @@ class NotificationReplyReceiver : BroadcastReceiver() {
|
||||
}
|
||||
}
|
||||
|
||||
val repliedNotification =
|
||||
NotificationCompat
|
||||
.Builder(context, ChatNotificationService.CHAT_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("Hermes")
|
||||
.setContentText("Replied")
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setAutoCancel(true)
|
||||
.build()
|
||||
val repliedNotification = buildReplyNotification(context)
|
||||
|
||||
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
manager.notify(ChatNotificationService.PENDING_NOTIFICATION_ID, repliedNotification)
|
||||
|
||||
Reference in new issue
Block a user