mirror of
https://github.com/anyproto/anytype-kotlin.git
synced 2025-06-07 21:37:02 +09:00
DROID-3634 Notifications | Handle pushKeyUpdates and store (#2379)
This commit is contained in:
parent
c0ba062b81
commit
1ef93d72db
8 changed files with 619 additions and 0 deletions
|
@ -1,16 +1,24 @@
|
|||
package com.anytypeio.anytype.di.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.anytypeio.anytype.app.AnytypeNotificationService
|
||||
import com.anytypeio.anytype.data.auth.event.NotificationsDateChannel
|
||||
import com.anytypeio.anytype.data.auth.event.NotificationsRemoteChannel
|
||||
import com.anytypeio.anytype.data.auth.event.PushKeyDataChannel
|
||||
import com.anytypeio.anytype.data.auth.event.PushKeyRemoteChannel
|
||||
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
|
||||
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
|
||||
import com.anytypeio.anytype.domain.chats.PushKeyChannel
|
||||
import com.anytypeio.anytype.domain.notifications.SystemNotificationService
|
||||
import com.anytypeio.anytype.domain.workspace.NotificationsChannel
|
||||
import com.anytypeio.anytype.middleware.EventProxy
|
||||
import com.anytypeio.anytype.middleware.interactor.EventHandlerChannel
|
||||
import com.anytypeio.anytype.middleware.interactor.NotificationsMiddlewareChannel
|
||||
import com.anytypeio.anytype.middleware.interactor.events.PushKeyMiddlewareChannel
|
||||
import com.anytypeio.anytype.presentation.notifications.NotificationsProvider
|
||||
import com.anytypeio.anytype.presentation.notifications.PushKeyProvider
|
||||
import com.anytypeio.anytype.presentation.notifications.PushKeyProviderImpl
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Named
|
||||
|
@ -59,4 +67,43 @@ object NotificationsModule {
|
|||
notificationsChannel = notificationsChannel,
|
||||
awaitAccountStartManager = awaitAccountStartManager,
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushKeyProvider(
|
||||
@Named("encrypted") sharedPreferences: SharedPreferences,
|
||||
dispatchers: AppCoroutineDispatchers,
|
||||
@Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
|
||||
channel: PushKeyChannel
|
||||
): PushKeyProvider {
|
||||
return PushKeyProviderImpl(
|
||||
sharedPreferences = sharedPreferences,
|
||||
dispatchers = dispatchers,
|
||||
scope = scope,
|
||||
channel = channel
|
||||
)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushKeyRemoteChannel(
|
||||
channel: EventHandlerChannel,
|
||||
@Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
|
||||
dispatchers: AppCoroutineDispatchers
|
||||
): PushKeyRemoteChannel = PushKeyMiddlewareChannel(
|
||||
channel = channel,
|
||||
scope = scope,
|
||||
dispatcher = dispatchers.io
|
||||
)
|
||||
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushKeyChannel(
|
||||
channel: PushKeyRemoteChannel
|
||||
): PushKeyChannel = PushKeyDataChannel(
|
||||
channel = channel
|
||||
)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
package com.anytypeio.anytype.core_models.chats
|
||||
|
||||
data class PushKeyUpdate(
|
||||
val encryptionKeyId: String,
|
||||
val encryptionKey: String
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = PushKeyUpdate(
|
||||
encryptionKeyId = "",
|
||||
encryptionKey = ""
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
package com.anytypeio.anytype.data.auth.event
|
||||
|
||||
import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
|
||||
import com.anytypeio.anytype.domain.chats.PushKeyChannel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PushKeyRemoteChannel {
|
||||
fun start()
|
||||
fun stop()
|
||||
fun observe(): Flow<PushKeyUpdate>
|
||||
}
|
||||
|
||||
class PushKeyDataChannel(
|
||||
private val channel: PushKeyRemoteChannel
|
||||
) : PushKeyChannel {
|
||||
|
||||
override fun observe(): Flow<PushKeyUpdate> {
|
||||
return channel.observe()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package com.anytypeio.anytype.domain.chats
|
||||
|
||||
import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface PushKeyChannel {
|
||||
fun observe(): Flow<PushKeyUpdate>
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package com.anytypeio.anytype.middleware.interactor.events
|
||||
|
||||
import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
|
||||
import com.anytypeio.anytype.core_utils.ext.cancel
|
||||
import com.anytypeio.anytype.data.auth.event.PushKeyRemoteChannel
|
||||
import com.anytypeio.anytype.middleware.interactor.EventHandlerChannel
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
class PushKeyMiddlewareChannel(
|
||||
private val scope: CoroutineScope,
|
||||
private val channel: EventHandlerChannel,
|
||||
private val dispatcher: CoroutineDispatcher
|
||||
) : PushKeyRemoteChannel {
|
||||
|
||||
private val jobs = mutableListOf<Job>()
|
||||
|
||||
private val _pushKeyStatus = MutableStateFlow<PushKeyUpdate>(PushKeyUpdate.EMPTY)
|
||||
val pushKeyStatus: Flow<PushKeyUpdate> = _pushKeyStatus.asStateFlow()
|
||||
|
||||
override fun start() {
|
||||
Timber.i("PushKeyMiddlewareChannel start")
|
||||
jobs.cancel()
|
||||
jobs += scope.launch(dispatcher) {
|
||||
channel.flow()
|
||||
.catch {
|
||||
Timber.w(it, "Error collecting push key updates")
|
||||
}
|
||||
.collect { emission ->
|
||||
emission.messages.forEach { message ->
|
||||
message.pushEncryptionKeyUpdate?.let {
|
||||
val pushKeyUpdate = PushKeyUpdate(
|
||||
encryptionKeyId = it.encryptionKeyId,
|
||||
encryptionKey = it.encryptionKey
|
||||
)
|
||||
_pushKeyStatus.value = pushKeyUpdate
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
Timber.i("PushKeyMiddlewareChannel stop")
|
||||
jobs.cancel()
|
||||
}
|
||||
|
||||
override fun observe(): Flow<PushKeyUpdate> {
|
||||
return pushKeyStatus
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
package com.anytypeio.anytype.middleware.interactor
|
||||
|
||||
import app.cash.turbine.test
|
||||
import app.cash.turbine.turbineScope
|
||||
import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
|
||||
import com.anytypeio.anytype.middleware.interactor.events.PushKeyMiddlewareChannel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.bytebuddy.utility.RandomString
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class PushKeyMiddlewareChannelTest {
|
||||
|
||||
private val dispatcher = StandardTestDispatcher(name = "Default test dispatcher")
|
||||
private val testScope = TestScope(dispatcher)
|
||||
lateinit var eventHandlerChannel: EventHandlerChannel
|
||||
private lateinit var channel: PushKeyMiddlewareChannel
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
eventHandlerChannel = EventHandlerChannelImpl()
|
||||
channel = PushKeyMiddlewareChannel(
|
||||
scope = testScope,
|
||||
channel = eventHandlerChannel,
|
||||
dispatcher = dispatcher
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit empty initially`() = runTest {
|
||||
// Given
|
||||
val initialValue = channel.observe().first()
|
||||
|
||||
// Then
|
||||
assertEquals(PushKeyUpdate.EMPTY, initialValue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit push key update when receiving two valid events`() = runTest(dispatcher) {
|
||||
|
||||
turbineScope {
|
||||
// Given
|
||||
val expectedUpdate1 = PushKeyUpdate(
|
||||
encryptionKeyId = RandomString.make(),
|
||||
encryptionKey = RandomString.make(),
|
||||
)
|
||||
|
||||
val event1 = anytype.Event(
|
||||
messages = listOf(
|
||||
anytype.Event.Message(
|
||||
pushEncryptionKeyUpdate = anytype.Event.PushEncryptionKey.Update(
|
||||
encryptionKeyId = expectedUpdate1.encryptionKeyId,
|
||||
encryptionKey = expectedUpdate1.encryptionKey
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val expectedUpdate2 = PushKeyUpdate(
|
||||
encryptionKeyId = RandomString.make(),
|
||||
encryptionKey = RandomString.make()
|
||||
)
|
||||
|
||||
val event2 = anytype.Event(
|
||||
messages = listOf(
|
||||
anytype.Event.Message(
|
||||
pushEncryptionKeyUpdate = anytype.Event.PushEncryptionKey.Update(
|
||||
encryptionKeyId = expectedUpdate2.encryptionKeyId,
|
||||
encryptionKey = expectedUpdate2.encryptionKey
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// When
|
||||
channel.start()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
channel.observe().test {
|
||||
|
||||
val emittedValue = awaitItem()
|
||||
assertEquals(PushKeyUpdate.EMPTY, emittedValue)
|
||||
|
||||
eventHandlerChannel.emit(event1)
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val update1 = awaitItem()
|
||||
assertEquals(expectedUpdate1, update1)
|
||||
|
||||
eventHandlerChannel.emit(event2)
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val update2 = awaitItem()
|
||||
assertEquals(expectedUpdate2, update2)
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not emit update when receiving invalid event`() = runTest(dispatcher) {
|
||||
turbineScope {
|
||||
// Given
|
||||
|
||||
val event = anytype.Event(
|
||||
messages = listOf(
|
||||
anytype.Event.Message(
|
||||
p2pStatusUpdate = anytype.Event.P2PStatus.Update(
|
||||
spaceId = RandomString.make()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// When
|
||||
channel.start()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
channel.observe().test {
|
||||
|
||||
val emittedValue = awaitItem()
|
||||
assertEquals(PushKeyUpdate.EMPTY, emittedValue)
|
||||
|
||||
eventHandlerChannel.emit(event)
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should stop processing events after stop is called`() = runTest(dispatcher) {
|
||||
|
||||
turbineScope {
|
||||
// Given
|
||||
|
||||
val expectedUpdate = PushKeyUpdate(
|
||||
encryptionKeyId = RandomString.make(),
|
||||
encryptionKey = RandomString.make(),
|
||||
)
|
||||
|
||||
val event = anytype.Event(
|
||||
messages = listOf(
|
||||
anytype.Event.Message(
|
||||
pushEncryptionKeyUpdate = anytype.Event.PushEncryptionKey.Update(
|
||||
encryptionKeyId = expectedUpdate.encryptionKeyId,
|
||||
encryptionKey = expectedUpdate.encryptionKey
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// When
|
||||
channel.start()
|
||||
channel.stop()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
channel.observe().test {
|
||||
|
||||
val emittedValue = awaitItem()
|
||||
assertEquals(PushKeyUpdate.EMPTY, emittedValue)
|
||||
|
||||
eventHandlerChannel.emit(event)
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle multiple messages in single emission`() = runTest {
|
||||
|
||||
turbineScope {
|
||||
turbineScope {
|
||||
// Given
|
||||
val expectedUpdate1 = PushKeyUpdate(
|
||||
encryptionKeyId = RandomString.make(),
|
||||
encryptionKey = RandomString.make(),
|
||||
)
|
||||
|
||||
val event1 = anytype.Event(
|
||||
messages = listOf(
|
||||
anytype.Event.Message(pushEncryptionKeyUpdate = null),
|
||||
anytype.Event.Message(
|
||||
pushEncryptionKeyUpdate = anytype.Event.PushEncryptionKey.Update(
|
||||
encryptionKeyId = expectedUpdate1.encryptionKeyId,
|
||||
encryptionKey = expectedUpdate1.encryptionKey
|
||||
)
|
||||
),
|
||||
anytype.Event.Message(pushEncryptionKeyUpdate = null),
|
||||
)
|
||||
)
|
||||
|
||||
// When
|
||||
channel.start()
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
// Then
|
||||
channel.observe().test {
|
||||
|
||||
val emittedValue = awaitItem()
|
||||
assertEquals(PushKeyUpdate.EMPTY, emittedValue)
|
||||
|
||||
eventHandlerChannel.emit(event1)
|
||||
dispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val update1 = awaitItem()
|
||||
assertEquals(expectedUpdate1, update1)
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue