diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt new file mode 100644 index 0000000000..2e182e81ae --- /dev/null +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/push/SyncOnNotifiableEventTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.push.impl.push + +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_UNIQUE_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.FakeMatrixClientProvider +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.sync.FakeSyncService +import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent +import io.element.android.services.appnavstate.test.FakeAppForegroundStateService +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class SyncOnNotifiableEventTest { + private val timelineItems = MutableStateFlow>(emptyList()) + private val syncStateFlow = MutableStateFlow(SyncState.Idle) + private val startSyncLambda = lambdaRecorder> { Result.success(Unit) } + private val stopSyncLambda = lambdaRecorder> { Result.success(Unit) } + private val subscribeToSyncLambda = lambdaRecorder { } + + private val liveTimeline = FakeTimeline( + timelineItems = timelineItems, + ) + private val room = FakeMatrixRoom( + roomId = A_ROOM_ID, + liveTimeline = liveTimeline, + subscribeToSyncLambda = subscribeToSyncLambda + ) + private val syncService = FakeSyncService(syncStateFlow).also { + it.startSyncLambda = startSyncLambda + it.stopSyncLambda = stopSyncLambda + } + + private val client = FakeMatrixClient( + syncService = syncService, + ).apply { + givenGetRoomResult(A_ROOM_ID, room) + } + + private val notifiableEvent = aNotifiableMessageEvent() + + @Test + fun `when feature flag is disabled, nothing happens`() = runTest { + val sut = createSyncOnNotifiableEvent(client = client, isSyncOnPushEnabled = false) + + sut(notifiableEvent) + + assert(startSyncLambda).isNeverCalled() + assert(stopSyncLambda).isNeverCalled() + assert(subscribeToSyncLambda).isNeverCalled() + } + + @Test + fun `when feature flag is enabled and app is in foreground, sync is not started`() = runTest { + val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = true, isSyncOnPushEnabled = true) + + sut(notifiableEvent) + + assert(startSyncLambda).isNeverCalled() + assert(stopSyncLambda).isNeverCalled() + assert(subscribeToSyncLambda).isCalledOnce() + } + + @Test + fun `when feature flag is enabled and app is in background, sync is started and stopped`() = runTest { + val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = false, isSyncOnPushEnabled = true) + + timelineItems.emit( + listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())) + ) + syncStateFlow.emit(SyncState.Running) + sut(notifiableEvent) + + assert(startSyncLambda).isCalledOnce() + assert(stopSyncLambda).isCalledOnce() + assert(subscribeToSyncLambda).isCalledOnce() + } + + @Test + fun `when feature flag is enabled and app is in background, running multiple time only call once`() = runTest { + val sut = createSyncOnNotifiableEvent(client = client, isAppInForeground = false, isSyncOnPushEnabled = true) + + coroutineScope { + launch { sut(notifiableEvent) } + launch { sut(notifiableEvent) } + launch { + delay(1) + timelineItems.emit( + listOf(MatrixTimelineItem.Event(A_UNIQUE_ID, anEventTimelineItem())) + ) + } + } + + assert(startSyncLambda).isCalledOnce() + assert(stopSyncLambda).isCalledOnce() + assert(subscribeToSyncLambda).isCalledExactly(2) + } + + private fun TestScope.createSyncOnNotifiableEvent( + client: MatrixClient = FakeMatrixClient(), + isSyncOnPushEnabled: Boolean = true, + isAppInForeground: Boolean = true, + ): SyncOnNotifiableEvent { + val featureFlagService = FakeFeatureFlagService( + initialState = mapOf( + FeatureFlags.SyncOnPush.key to isSyncOnPushEnabled + ) + ) + val appForegroundStateService = FakeAppForegroundStateService( + initialValue = isAppInForeground + ) + val matrixClientProvider = FakeMatrixClientProvider { Result.success(client) } + return SyncOnNotifiableEvent( + matrixClientProvider = matrixClientProvider, + featureFlagService = featureFlagService, + appForegroundStateService = appForegroundStateService, + dispatchers = testCoroutineDispatchers(), + ) + } +}