diff --git a/android/src/test/java/com/rssuper/services/NotificationServiceTest.kt b/android/src/test/java/com/rssuper/services/NotificationServiceTest.kt new file mode 100644 index 0000000..52744e7 --- /dev/null +++ b/android/src/test/java/com/rssuper/services/NotificationServiceTest.kt @@ -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()) + } + + @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() + whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager) + whenever(mockNotificationManager.notify(any(), any())).doReturn(true) + + val result = notificationService.showNotification("Test Title", "Test Body") + + assertTrue(result) + } + + @Test + fun testShowNotification_permissionDenied() = runTest { + val mockNotificationManager = mock() + whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager) + whenever(mockNotificationManager.notify(any(), any())).doReturn(false) + + val result = notificationService.showNotification("Test Title", "Test Body") + + assertFalse(result) + } + + @Test + fun testShowLocalNotification() = runTest { + val mockNotificationManager = mock() + whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager) + whenever(mockNotificationManager.notify(any(), any())).doReturn(true) + + val result = notificationService.showLocalNotification("Test Title", "Test Body") + + assertTrue(result) + } + + @Test + fun testShowPushNotification() = runTest { + val mockNotificationManager = mock() + whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager) + whenever(mockNotificationManager.notify(any(), any())).doReturn(true) + + val result = notificationService.showPushNotification("Test Title", "Test Body") + + assertTrue(result) + } + + @Test + fun testShowNotificationWithAction() = runTest { + val mockNotificationManager = mock() + whenever(NotificationManagerCompat.from(context)).doReturn(mockNotificationManager) + whenever(mockNotificationManager.notify(any(), any())).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() + 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) + } +} diff --git a/native-route/android/app/src/main/java/com/rssuper/NotificationService.kt b/native-route/android/app/src/main/java/com/rssuper/NotificationService.kt index 1668ffb..a980227 100644 --- a/native-route/android/app/src/main/java/com/rssuper/NotificationService.kt +++ b/native-route/android/app/src/main/java/com/rssuper/NotificationService.kt @@ -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) diff --git a/native-route/android/app/src/test/java/com/rssuper/NotificationServiceTests.kt b/native-route/android/app/src/test/java/com/rssuper/NotificationServiceTests.kt new file mode 100644 index 0000000..4acbba7 --- /dev/null +++ b/native-route/android/app/src/test/java/com/rssuper/NotificationServiceTests.kt @@ -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() { + + 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() { + + 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() { + + 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) + } +}