1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3634 Notifications | Handle pushKeyUpdates and store (#2379)

This commit is contained in:
Konstantin Ivanov 2025-05-06 11:28:59 +02:00 committed by GitHub
parent c0ba062b81
commit 1ef93d72db
Signed by: github
GPG key ID: B5690EEEBB952194
8 changed files with 619 additions and 0 deletions

View file

@ -0,0 +1,65 @@
package com.anytypeio.anytype.presentation.notifications
import android.content.SharedPreferences
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.chats.PushKeyChannel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
interface PushKeyProvider {
fun getPushKey(): PushKey
}
data class PushKey(
val key: String,
val id: String
) {
companion object {
val EMPTY = PushKey(key = "", id = "")
}
}
class PushKeyProviderImpl @Inject constructor(
private val sharedPreferences: SharedPreferences,
private val channel: PushKeyChannel,
dispatchers: AppCoroutineDispatchers,
scope: CoroutineScope
) : PushKeyProvider {
init {
Timber.d("PushKeyProvider initialized")
scope.launch(dispatchers.io) {
channel
.observe()
.collect { event ->
Timber.d("New push key updates: $event")
savePushKey(
pushKey = event.encryptionKey,
pushKeyId = event.encryptionKeyId
)
}
}
}
private fun savePushKey(pushKey: String?, pushKeyId: String?) {
sharedPreferences.edit().apply {
putString(PREF_PUSH_KEY, pushKey)
putString(PREF_PUSH_KEY_ID, pushKeyId)
apply()
}
}
override fun getPushKey(): PushKey {
val pushKey = sharedPreferences.getString(PREF_PUSH_KEY, "") ?: ""
val pushKeyId = sharedPreferences.getString(PREF_PUSH_KEY_ID, "") ?: ""
Timber.d("PushKeyProvider getPushKey: $pushKey, $pushKeyId")
return PushKey(key = pushKey, id = pushKeyId)
}
companion object {
const val PREF_PUSH_KEY = "pref.push_key"
const val PREF_PUSH_KEY_ID = "pref.push_key_id"
}
}

View file

@ -0,0 +1,181 @@
package com.anytypeio.anytype.presentation.notifications
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.chats.PushKeyChannel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.stub
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [Build.VERSION_CODES.P])
class PushKeyProviderImplTest {
@Mock
private lateinit var mockChannel: PushKeyChannel
private lateinit var sharedPreferences: SharedPreferences
private val dispatcher = StandardTestDispatcher(name = "Default test dispatcher")
private val testScope = TestScope(dispatcher)
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
// Use a real SharedPreferences from Robolectric
val context = ApplicationProvider.getApplicationContext<Context>()
sharedPreferences = context.getSharedPreferences("test_prefs", Context.MODE_PRIVATE)
// Clear shared preferences before each test
sharedPreferences.edit().clear().apply()
}
@After
fun tearDown() {
// Clean up coroutines
testScope.cancel()
// Clear shared preferences after each test
sharedPreferences.edit().clear().apply()
}
@Test
fun `getPushKey should return empty PushKey when no key is set`() = runTest {
val pushKeyProvider = createPushKeyProvider()
val pushKey = pushKeyProvider.getPushKey()
assertTrue(pushKey == PushKey.EMPTY)
}
@Test
fun `savePushKey should store the key and keyId in shared preferences when channel emits`() =
runTest(dispatcher) {
val pushKey = "test_push_key"
val pushKeyId = "test_push_key_id"
// Simulate the event emission from the channel
val channelFlow = MutableSharedFlow<PushKeyUpdate>(replay = 0)
mockChannel.stub {
on { observe() } doReturn channelFlow
}
createPushKeyProvider() // This will start observing the channel
dispatcher.scheduler.advanceUntilIdle() // Allow the observation coroutine to run
// Emit the event
channelFlow.emit(PushKeyUpdate(encryptionKey = pushKey, encryptionKeyId = pushKeyId))
dispatcher.scheduler.advanceUntilIdle() // Allow the processing coroutine to run
// Verify the stored values in SharedPreferences
val storedKey = sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY, null)
val storedKeyId =
sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY_ID, null)
assertEquals(pushKey, storedKey)
assertEquals(pushKeyId, storedKeyId)
}
@Test
fun `getPushKey should return the stored key when a key is set`() = runTest {
val pushKey = PushKey(key = "test_push_key", id = "test_push_key_id")
sharedPreferences.edit()
.putString(PushKeyProviderImpl.PREF_PUSH_KEY_ID, pushKey.id)
.apply()
sharedPreferences.edit()
.putString(PushKeyProviderImpl.PREF_PUSH_KEY, pushKey.key)
.apply()
val pushKeyProvider = createPushKeyProvider()
val retrievedKey = pushKeyProvider.getPushKey()
assertEquals(pushKey, retrievedKey)
}
@Test
fun `observation should update shared preferences on subsequent channel emissions`() =
runTest(dispatcher) {
val initialKey = "initial_key"
val initialKeyId = "initial_key_id"
val updatedKey = "updated_key"
val updatedKeyId = "updated_key_id"
// Manually control the flow emission
val channelFlow = MutableSharedFlow<PushKeyUpdate>(replay = 0)
mockChannel.stub {
on { observe() } doReturn channelFlow
}
createPushKeyProvider() // Start observing
dispatcher.scheduler.advanceUntilIdle() // Ensure observation is set up
// Emit initial event
channelFlow.emit(
PushKeyUpdate(
encryptionKey = initialKey,
encryptionKeyId = initialKeyId
)
)
dispatcher.scheduler.advanceUntilIdle() // Process emission
// Verify initial storage
assertEquals(
initialKey,
sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY, null)
)
assertEquals(
initialKeyId,
sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY_ID, null)
)
// Emit updated event
channelFlow.emit(
PushKeyUpdate(
encryptionKey = updatedKey,
encryptionKeyId = updatedKeyId
)
)
dispatcher.scheduler.advanceUntilIdle() // Process emission
// Verify updated storage
assertEquals(
updatedKey,
sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY, null)
)
assertEquals(
updatedKeyId,
sharedPreferences.getString(PushKeyProviderImpl.PREF_PUSH_KEY_ID, null)
)
}
private fun createPushKeyProvider(): PushKeyProviderImpl {
return PushKeyProviderImpl(
sharedPreferences = sharedPreferences,
channel = mockChannel,
dispatchers = AppCoroutineDispatchers(
io = dispatcher,
main = dispatcher,
computation = dispatcher
),
scope = testScope
)
}
}