Fix Android notification service code review issues
- Fix invalid notificationManager extension property (line 60) - Remove invalid getChannelId() calls (line 124) - Fix NotificationCompat.Builder to use method chaining (line 140) - Remove undefined newIntent() call (line 154) - Add unit tests for NotificationService, NotificationManager, NotificationPreferences All fixes address issues identified in code review for FRE-536.
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
package com.rssuper.services
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PermissionGrantState
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import com.nhaarman.mockitokotlin2.any
|
||||
import com.nhaarman.mockitokotlin2.doReturn
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
|
||||
class NotificationServiceTest {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
private lateinit var notificationService: NotificationService
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
context = mock()
|
||||
notificationManager = mock()
|
||||
notificationService = NotificationService(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateNotificationChannels_OreoPlus() = runTest {
|
||||
whenever(context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.doReturn(notificationManager)
|
||||
|
||||
notificationService.createNotificationChannels()
|
||||
|
||||
verify(notificationManager).createNotificationChannel(any<NotificationChannel>())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasNotificationPermission_belowTiramisu() = runTest {
|
||||
whenever(context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS))
|
||||
.doReturn(PackageManager.PERMISSION_GRANTED)
|
||||
|
||||
val hasPermission = notificationService.hasNotificationPermission()
|
||||
|
||||
assertTrue(hasPermission)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasNotificationPermission_granted() = runTest {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
whenever(context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS))
|
||||
.doReturn(PackageManager.PERMISSION_GRANTED)
|
||||
|
||||
val hasPermission = notificationService.hasNotificationPermission()
|
||||
|
||||
assertTrue(hasPermission)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testHasNotificationPermission_denied() = runTest {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
whenever(context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS))
|
||||
.doReturn(PackageManager.PERMISSION_DENIED)
|
||||
|
||||
val hasPermission = notificationService.hasNotificationPermission()
|
||||
|
||||
assertFalse(hasPermission)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowNotification_permissionGranted() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManagerCompat>()
|
||||
whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager)
|
||||
whenever(mockNotificationManager.notify(any<Int>(), any<Notification>())).doReturn(true)
|
||||
|
||||
val result = notificationService.showNotification("Test Title", "Test Body")
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowNotification_permissionDenied() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManagerCompat>()
|
||||
whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager)
|
||||
whenever(mockNotificationManager.notify(any<Int>(), any<Notification>())).doReturn(false)
|
||||
|
||||
val result = notificationService.showNotification("Test Title", "Test Body")
|
||||
|
||||
assertFalse(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowLocalNotification() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManagerCompat>()
|
||||
whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager)
|
||||
whenever(mockNotificationManager.notify(any<Int>(), any<Notification>())).doReturn(true)
|
||||
|
||||
val result = notificationService.showLocalNotification("Test Title", "Test Body")
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowPushNotification() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManagerCompat>()
|
||||
whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager)
|
||||
whenever(mockNotificationManager.notify(any<Int>(), any<Notification>())).doReturn(true)
|
||||
|
||||
val result = notificationService.showPushNotification("Test Title", "Test Body")
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testShowNotificationWithAction() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManagerCompat>()
|
||||
whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager)
|
||||
whenever(mockNotificationManager.notify(any<Int>(), any<Notification>())).doReturn(true)
|
||||
|
||||
val actionIntent: Intent = mock()
|
||||
val result = notificationService.showNotificationWithAction(
|
||||
"Test Title",
|
||||
"Test Body",
|
||||
"Action Label",
|
||||
PendingIntent.getActivity(context, 0, actionIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
)
|
||||
|
||||
assertTrue(result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateBadgeCount() = runTest {
|
||||
notificationService.updateBadgeCount(5)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClearAllNotifications() = runTest {
|
||||
val mockNotificationManager = mock<NotificationManager>()
|
||||
whenever(context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.doReturn(mockNotificationManager)
|
||||
|
||||
notificationService.clearAllNotifications()
|
||||
|
||||
verify(mockNotificationManager).cancelAll()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testGetPreferences() = runTest {
|
||||
val preferences = notificationService.getPreferences()
|
||||
|
||||
assertNotNull(preferences)
|
||||
assertEquals(true, preferences.newArticles)
|
||||
assertEquals(true, preferences.episodeReleases)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSavePreferences() = runTest {
|
||||
val preferences = NotificationPreferences(
|
||||
newArticles = true,
|
||||
episodeReleases = false
|
||||
)
|
||||
|
||||
notificationService.savePreferences(preferences)
|
||||
|
||||
val retrieved = notificationService.getPreferences()
|
||||
assertEquals(true, retrieved.newArticles)
|
||||
assertEquals(false, retrieved.episodeReleases)
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ class NotificationService : Service() {
|
||||
* Create notification channels
|
||||
*/
|
||||
private fun createNotificationChannels() {
|
||||
val notificationManager = context?.notificationManager
|
||||
val notificationManager = context?.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
||||
|
||||
// Critical notifications channel
|
||||
val criticalChannel = NotificationChannel(
|
||||
@@ -119,9 +119,9 @@ class NotificationService : Service() {
|
||||
) {
|
||||
val notificationManager = notificationManager ?: return
|
||||
|
||||
// Get appropriate notification channel
|
||||
val channel: NotificationChannel? = when (urgency) {
|
||||
NotificationUrgency.CRITICAL -> { notificationManager.getChannelId(NOTIFICATION_CHANNEL_ID_CRITICAL) } else -> { notificationManager.getChannelId(NOTIFICATION_CHANNEL_ID) }
|
||||
val channelId = when (urgency) {
|
||||
NotificationUrgency.CRITICAL -> NOTIFICATION_CHANNEL_ID_CRITICAL
|
||||
else -> NOTIFICATION_CHANNEL_ID
|
||||
}
|
||||
|
||||
// Create notification intent
|
||||
@@ -137,21 +137,18 @@ class NotificationService : Service() {
|
||||
)
|
||||
|
||||
// Create notification builder
|
||||
val builder = NotificationCompat.Builder(this, channel) {
|
||||
setSmallIcon(icon)
|
||||
setAutoCancel(true)
|
||||
setPriority(when (urgency) {
|
||||
val builder = NotificationCompat.Builder(this, channelId)
|
||||
.setSmallIcon(icon)
|
||||
.setAutoCancel(true)
|
||||
.setPriority(when (urgency) {
|
||||
NotificationUrgency.CRITICAL -> NotificationCompat.PRIORITY_HIGH
|
||||
NotificationUrgency.LOW -> NotificationCompat.PRIORITY_LOW
|
||||
else -> NotificationCompat.PRIORITY_DEFAULT
|
||||
})
|
||||
setContentTitle(title)
|
||||
setContentText(text)
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
}
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setStyle(NotificationCompat.BigTextStyle().bigText(text))
|
||||
|
||||
// Add extra data
|
||||
builder.setExtras(newIntent())
|
||||
builder.setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||
builder.setSound(null)
|
||||
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
package com.rssuper
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.test.core.app.ApplicationTestCase
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mockito.*
|
||||
|
||||
/**
|
||||
* NotificationServiceTests - Unit tests for NotificationService
|
||||
*/
|
||||
class NotificationServiceTests : ApplicationTestCase<Context>() {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var notificationService: NotificationService
|
||||
|
||||
override val packageName: String get() = "com.rssuper"
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = getTargetContext()
|
||||
notificationService = NotificationService()
|
||||
notificationService.initialize(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_initialization() {
|
||||
assertNotNull("NotificationService should be initialized", notificationService)
|
||||
assertNotNull("Context should be set", notificationService.getContext())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_getInstance() {
|
||||
val instance = notificationService.getInstance()
|
||||
assertNotNull("Instance should not be null", instance)
|
||||
assertEquals("Instance should be the same object", notificationService, instance)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_getNotificationId() {
|
||||
assertEquals("Notification ID should be 1001", 1001, notificationService.getNotificationId())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_getService() {
|
||||
val service = notificationService.getService()
|
||||
assertNotNull("Service should not be null", service)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationUrgency_values() {
|
||||
assertEquals("CRITICAL should be 0", 0, NotificationUrgency.CRITICAL.ordinal)
|
||||
assertEquals("LOW should be 1", 1, NotificationUrgency.LOW.ordinal)
|
||||
assertEquals("NORMAL should be 2", 2, NotificationUrgency.NORMAL.ordinal)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationUrgency_critical() {
|
||||
assertEquals("Critical urgency should be CRITICAL", NotificationUrgency.CRITICAL, NotificationUrgency.CRITICAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationUrgency_low() {
|
||||
assertEquals("Low urgency should be LOW", NotificationUrgency.LOW, NotificationUrgency.LOW)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationUrgency_normal() {
|
||||
assertEquals("Normal urgency should be NORMAL", NotificationUrgency.NORMAL, NotificationUrgency.NORMAL)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_showCriticalNotification() {
|
||||
// Test that showCriticalNotification calls showNotification with CRITICAL urgency
|
||||
// Note: This is a basic test - actual notification display would require Android environment
|
||||
val service = NotificationService()
|
||||
service.initialize(context)
|
||||
|
||||
// Verify the method exists and can be called
|
||||
assertDoesNotThrow {
|
||||
service.showCriticalNotification("Test Title", "Test Text", 0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_showLowNotification() {
|
||||
val service = NotificationService()
|
||||
service.initialize(context)
|
||||
|
||||
assertDoesNotThrow {
|
||||
service.showLowNotification("Test Title", "Test Text", 0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationService_showNormalNotification() {
|
||||
val service = NotificationService()
|
||||
service.initialize(context)
|
||||
|
||||
assertDoesNotThrow {
|
||||
service.showNormalNotification("Test Title", "Test Text", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationManagerTests - Unit tests for NotificationManager
|
||||
*/
|
||||
class NotificationManagerTests : ApplicationTestCase<Context>() {
|
||||
|
||||
private lateinit var context: Context
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
|
||||
override val packageName: String get() = "com.rssuper"
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = getTargetContext()
|
||||
notificationManager = NotificationManager(context)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_initialization() {
|
||||
assertNotNull("NotificationManager should be initialized", notificationManager)
|
||||
assertNotNull("Context should be set", notificationManager.getContext())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_getPreferences_defaultValues() {
|
||||
val prefs = notificationManager.getPreferences()
|
||||
|
||||
assertTrue("newArticles should default to true", prefs.newArticles)
|
||||
assertTrue("episodeReleases should default to true", prefs.episodeReleases)
|
||||
assertTrue("customAlerts should default to true", prefs.customAlerts)
|
||||
assertTrue("badgeCount should default to true", prefs.badgeCount)
|
||||
assertTrue("sound should default to true", prefs.sound)
|
||||
assertTrue("vibration should default to true", prefs.vibration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_setPreferences() {
|
||||
val preferences = NotificationPreferences(
|
||||
newArticles = false,
|
||||
episodeReleases = false,
|
||||
customAlerts = false,
|
||||
badgeCount = false,
|
||||
sound = false,
|
||||
vibration = false
|
||||
)
|
||||
|
||||
assertDoesNotThrow {
|
||||
notificationManager.setPreferences(preferences)
|
||||
}
|
||||
|
||||
val loadedPrefs = notificationManager.getPreferences()
|
||||
assertEquals("newArticles should match", preferences.newArticles, loadedPrefs.newArticles)
|
||||
assertEquals("episodeReleases should match", preferences.episodeReleases, loadedPrefs.episodeReleases)
|
||||
assertEquals("customAlerts should match", preferences.customAlerts, loadedPrefs.customAlerts)
|
||||
assertEquals("badgeCount should match", preferences.badgeCount, loadedPrefs.badgeCount)
|
||||
assertEquals("sound should match", preferences.sound, loadedPrefs.sound)
|
||||
assertEquals("vibration should match", preferences.vibration, loadedPrefs.vibration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_getNotificationService() {
|
||||
val service = notificationManager.getNotificationService()
|
||||
assertNotNull("NotificationService should not be null", service)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_getNotificationManager() {
|
||||
val mgr = notificationManager.getNotificationManager()
|
||||
assertNotNull("NotificationManager should not be null", mgr)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_getAppIntent() {
|
||||
val intent = notificationManager.getAppIntent()
|
||||
assertNotNull("Intent should not be null", intent)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationManager_getPrefsName() {
|
||||
assertEquals("Prefs name should be notification_prefs", "notification_prefs", notificationManager.getPrefsName())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NotificationPreferencesTests - Unit tests for NotificationPreferences data class
|
||||
*/
|
||||
class NotificationPreferencesTests : ApplicationTestCase<Context>() {
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
override val packageName: String get() = "com.rssuper"
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
context = getTargetContext()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_defaultValues() {
|
||||
val prefs = NotificationPreferences()
|
||||
|
||||
assertTrue("newArticles should default to true", prefs.newArticles)
|
||||
assertTrue("episodeReleases should default to true", prefs.episodeReleases)
|
||||
assertTrue("customAlerts should default to true", prefs.customAlerts)
|
||||
assertTrue("badgeCount should default to true", prefs.badgeCount)
|
||||
assertTrue("sound should default to true", prefs.sound)
|
||||
assertTrue("vibration should default to true", prefs.vibration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_customValues() {
|
||||
val prefs = NotificationPreferences(
|
||||
newArticles = false,
|
||||
episodeReleases = false,
|
||||
customAlerts = false,
|
||||
badgeCount = false,
|
||||
sound = false,
|
||||
vibration = false
|
||||
)
|
||||
|
||||
assertFalse("newArticles should be false", prefs.newArticles)
|
||||
assertFalse("episodeReleases should be false", prefs.episodeReleases)
|
||||
assertFalse("customAlerts should be false", prefs.customAlerts)
|
||||
assertFalse("badgeCount should be false", prefs.badgeCount)
|
||||
assertFalse("sound should be false", prefs.sound)
|
||||
assertFalse("vibration should be false", prefs.vibration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_partialValues() {
|
||||
val prefs = NotificationPreferences(newArticles = false, sound = false)
|
||||
|
||||
assertFalse("newArticles should be false", prefs.newArticles)
|
||||
assertTrue("episodeReleases should default to true", prefs.episodeReleases)
|
||||
assertTrue("customAlerts should default to true", prefs.customAlerts)
|
||||
assertTrue("badgeCount should default to true", prefs.badgeCount)
|
||||
assertFalse("sound should be false", prefs.sound)
|
||||
assertTrue("vibration should default to true", prefs.vibration)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_equality() {
|
||||
val prefs1 = NotificationPreferences(
|
||||
newArticles = true,
|
||||
episodeReleases = false,
|
||||
customAlerts = true,
|
||||
badgeCount = false,
|
||||
sound = true,
|
||||
vibration = false
|
||||
)
|
||||
|
||||
val prefs2 = NotificationPreferences(
|
||||
newArticles = true,
|
||||
episodeReleases = false,
|
||||
customAlerts = true,
|
||||
badgeCount = false,
|
||||
sound = true,
|
||||
vibration = false
|
||||
)
|
||||
|
||||
assertEquals("Preferences with same values should be equal", prefs1, prefs2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_hashCode() {
|
||||
val prefs1 = NotificationPreferences()
|
||||
val prefs2 = NotificationPreferences()
|
||||
|
||||
assertEquals("Equal objects should have equal hash codes", prefs1.hashCode(), prefs2.hashCode())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotificationPreferences_copy() {
|
||||
val prefs1 = NotificationPreferences(newArticles = false)
|
||||
val prefs2 = prefs1.copy(newArticles = true)
|
||||
|
||||
assertFalse("prefs1 newArticles should be false", prefs1.newArticles)
|
||||
assertTrue("prefs2 newArticles should be true", prefs2.newArticles)
|
||||
assertEquals("prefs2 should have same other values", prefs1.episodeReleases, prefs2.episodeReleases)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user