android: add datastore notification settings and gating usecase
Some checks failed
CI / test (push) Failing after 2m6s

This commit is contained in:
Codex
2026-03-09 15:02:56 +03:00
parent dfa67c34c9
commit 33514265e3
12 changed files with 341 additions and 9 deletions

View File

@@ -2,14 +2,14 @@ package ru.daemonlord.messenger.core.token
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.PreferenceDataStoreFactory
import androidx.datastore.preferences.core.emptyPreferences
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import okio.Path.Companion.toPath
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
import java.io.File
@OptIn(ExperimentalCoroutinesApi::class)
class DataStoreTokenRepositoryTest {
@@ -46,10 +46,18 @@ class DataStoreTokenRepositoryTest {
}
private fun createTestDataStore(): DataStore<Preferences> {
val file = File.createTempFile("tokens", ".preferences_pb")
file.deleteOnExit()
return PreferenceDataStoreFactory.createWithPath(
produceFile = { file.absolutePath.toPath() }
)
return InMemoryPreferencesDataStore()
}
private class InMemoryPreferencesDataStore : DataStore<Preferences> {
private val state = MutableStateFlow<Preferences>(emptyPreferences())
override val data: Flow<Preferences> = state
override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences {
val updated = transform(state.value)
state.value = updated
return updated
}
}
}

View File

@@ -0,0 +1,67 @@
package ru.daemonlord.messenger.data.notifications.repository
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.emptyPreferences
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
@OptIn(ExperimentalCoroutinesApi::class)
class DataStoreNotificationSettingsRepositoryTest {
@Test
fun saveAndReadGlobalSettings_returnsPersistedValues() = runTest {
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
repository.setGlobalEnabled(enabled = false)
repository.setPreviewEnabled(enabled = false)
val settings = repository.getSettings()
assertFalse(settings.globalEnabled)
assertFalse(settings.previewEnabled)
}
@Test
fun saveAndReadChatOverride_returnsPersistedValue() = runTest {
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
repository.setChatOverride(chatId = 42L, mode = ChatNotificationOverride.MUTED)
val mode = repository.getChatOverride(chatId = 42L)
assertEquals(ChatNotificationOverride.MUTED, mode)
}
@Test
fun clearChatOverride_resetsToDefault() = runTest {
val repository = DataStoreNotificationSettingsRepository(createTestDataStore())
repository.setChatOverride(chatId = 42L, mode = ChatNotificationOverride.ENABLED)
repository.clearChatOverride(chatId = 42L)
val mode = repository.getChatOverride(chatId = 42L)
assertTrue(mode == ChatNotificationOverride.DEFAULT)
}
private fun createTestDataStore(): DataStore<Preferences> {
return InMemoryPreferencesDataStore()
}
private class InMemoryPreferencesDataStore : DataStore<Preferences> {
private val state = MutableStateFlow<Preferences>(emptyPreferences())
override val data: Flow<Preferences> = state
override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences {
val updated = transform(state.value)
state.value = updated
return updated
}
}
}

View File

@@ -0,0 +1,92 @@
package ru.daemonlord.messenger.domain.notifications.usecase
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import ru.daemonlord.messenger.domain.notifications.model.ChatNotificationOverride
import ru.daemonlord.messenger.domain.notifications.model.NotificationSettings
import ru.daemonlord.messenger.domain.notifications.repository.NotificationSettingsRepository
@OptIn(ExperimentalCoroutinesApi::class)
class ShouldShowMessageNotificationUseCaseTest {
@Test
fun globalDisabled_blocksNotifications() = runTest {
val repository = FakeNotificationSettingsRepository(
settings = NotificationSettings(globalEnabled = false, previewEnabled = true),
)
val useCase = ShouldShowMessageNotificationUseCase(repository)
val result = useCase(chatId = 1L, isMention = true, serverMuted = false)
assertFalse(result)
}
@Test
fun mutedChat_allowsMention() = runTest {
val repository = FakeNotificationSettingsRepository(
settings = NotificationSettings(globalEnabled = true, previewEnabled = true),
overrides = mutableMapOf(1L to ChatNotificationOverride.MUTED),
)
val useCase = ShouldShowMessageNotificationUseCase(repository)
val result = useCase(chatId = 1L, isMention = true, serverMuted = true)
assertTrue(result)
}
@Test
fun chatOverrideEnabled_unmutesServerMutedChat() = runTest {
val repository = FakeNotificationSettingsRepository(
settings = NotificationSettings(globalEnabled = true, previewEnabled = true),
overrides = mutableMapOf(1L to ChatNotificationOverride.ENABLED),
)
val useCase = ShouldShowMessageNotificationUseCase(repository)
val result = useCase(chatId = 1L, isMention = false, serverMuted = true)
assertTrue(result)
}
private class FakeNotificationSettingsRepository(
settings: NotificationSettings,
overrides: MutableMap<Long, ChatNotificationOverride> = mutableMapOf(),
) : NotificationSettingsRepository {
private val settingsFlow = MutableStateFlow(settings)
private val chatOverrides = overrides
override fun observeSettings(): Flow<NotificationSettings> = settingsFlow
override suspend fun getSettings(): NotificationSettings = settingsFlow.value
override suspend fun setGlobalEnabled(enabled: Boolean) {
settingsFlow.value = settingsFlow.value.copy(globalEnabled = enabled)
}
override suspend fun setPreviewEnabled(enabled: Boolean) {
settingsFlow.value = settingsFlow.value.copy(previewEnabled = enabled)
}
override fun observeChatOverride(chatId: Long): Flow<ChatNotificationOverride> {
return settingsFlow.map { chatOverrides[chatId] ?: ChatNotificationOverride.DEFAULT }
}
override suspend fun getChatOverride(chatId: Long): ChatNotificationOverride {
return chatOverrides[chatId] ?: ChatNotificationOverride.DEFAULT
}
override suspend fun setChatOverride(chatId: Long, mode: ChatNotificationOverride) {
chatOverrides[chatId] = mode
}
override suspend fun clearChatOverride(chatId: Long) {
chatOverrides.remove(chatId)
}
}
}