From 41825812352f6cf07acabab983c65205dc095698 Mon Sep 17 00:00:00 2001 From: Maxime NATUREL <46314705+mnaturel@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:51:16 +0100 Subject: [PATCH] Adding unit tests for SyncPollsTask --- .../session/room/poll/SyncPollsTask.kt | 9 +- .../room/poll/DefaultSyncPollsTaskTest.kt | 129 ++++++++++++++++++ .../android/sdk/test/fakes/FakeTimeline.kt | 40 ++++++ 3 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt create mode 100644 matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt index 4a83f0f870..fff24288b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/poll/SyncPollsTask.kt @@ -84,18 +84,17 @@ internal class DefaultSyncPollsTask @Inject constructor( ): LoadStatus { return monarchy.awaitTransaction { realm -> val status = PollHistoryStatusEntity.getOrCreate(realm, roomId) - val mostRecentEventIdReached = status.mostRecentEventIdReached - val mostRecentEvent = events .maxByOrNull { it.root.originServerTs ?: Long.MIN_VALUE } ?.root + val mostRecentEventIdReached = mostRecentEvent?.eventId - if (mostRecentEventIdReached == null) { + if (mostRecentEventIdReached != null) { // save it for next forward pagination - status.mostRecentEventIdReached = mostRecentEvent?.eventId + status.mostRecentEventIdReached = mostRecentEventIdReached } - val mostRecentTimestamp = mostRecentEvent?.ageLocalTs + val mostRecentTimestamp = mostRecentEvent?.originServerTs val shouldLoadMore = paginationState.hasMoreToLoad && (mostRecentTimestamp == null || mostRecentTimestamp < currentTimestampMs) diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt new file mode 100644 index 0000000000..8a95a2f131 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/poll/DefaultSyncPollsTaskTest.kt @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * 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 + * + * http://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 org.matrix.android.sdk.internal.session.room.poll + +import io.mockk.coVerifyOrder +import io.mockk.every +import io.mockk.mockk +import io.mockk.unmockkAll +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.amshove.kluent.shouldBeEqualTo +import org.junit.After +import org.junit.Test +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntity +import org.matrix.android.sdk.internal.database.model.PollHistoryStatusEntityFields +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.FakeTimeline +import org.matrix.android.sdk.test.fakes.givenEqualTo +import org.matrix.android.sdk.test.fakes.givenFindFirst + +private const val A_ROOM_ID = "room-id" +private const val A_TIMESTAMP = 123L +private const val A_PAGE_SIZE = 200 + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultSyncPollsTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val fakeTimeline = FakeTimeline() + + private val defaultSyncPollsTask = DefaultSyncPollsTask( + monarchy = fakeMonarchy.instance, + ) + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given timeline when execute then more events are fetched in forward direction after the most recent event id reached`() = runTest { + // Given + val params = givenTaskParams() + val mostRecentEventId = "most-recent" + val oldestEventId = "oldest" + val pollHistoryStatus = aPollHistoryStatusEntity( + mostRecentEventIdReached = mostRecentEventId, + oldestEventIdReached = oldestEventId, + ) + fakeMonarchy.fakeRealm + .givenWhere() + .givenEqualTo(PollHistoryStatusEntityFields.ROOM_ID, A_ROOM_ID) + .givenFindFirst(pollHistoryStatus) + fakeTimeline.givenRestartWithEventIdSuccess(mostRecentEventId) + fakeTimeline.givenRestartWithEventIdSuccess(oldestEventId) + val anEventId = "event-id" + val aTimelineEvent = aTimelineEvent(anEventId) + fakeTimeline.givenAwaitPaginateReturns( + events = listOf(aTimelineEvent), + direction = Timeline.Direction.FORWARDS, + count = params.eventsPageSize, + ) + fakeTimeline.givenGetPaginationStateReturns( + paginationState = aPaginationState(), + direction = Timeline.Direction.FORWARDS, + ) + + // When + defaultSyncPollsTask.execute(params) + + // Then + coVerifyOrder { + fakeTimeline.instance.restartWithEventId(mostRecentEventId) + fakeTimeline.instance.awaitPaginate(direction = Timeline.Direction.FORWARDS, count = params.eventsPageSize) + fakeTimeline.instance.getPaginationState(direction = Timeline.Direction.FORWARDS) + fakeTimeline.instance.restartWithEventId(oldestEventId) + } + pollHistoryStatus.mostRecentEventIdReached shouldBeEqualTo anEventId + } + + private fun givenTaskParams(): SyncPollsTask.Params { + return SyncPollsTask.Params( + timeline = fakeTimeline.instance, + roomId = A_ROOM_ID, + currentTimestampMs = A_TIMESTAMP, + eventsPageSize = A_PAGE_SIZE, + ) + } + + private fun aPollHistoryStatusEntity( + mostRecentEventIdReached: String, + oldestEventIdReached: String, + ): PollHistoryStatusEntity { + return PollHistoryStatusEntity( + roomId = A_ROOM_ID, + mostRecentEventIdReached = mostRecentEventIdReached, + oldestEventIdReached = oldestEventIdReached, + ) + } + + private fun aTimelineEvent(eventId: String): TimelineEvent { + val event = mockk() + every { event.root.originServerTs } returns 123L + every { event.root.eventId } returns eventId + return event + } + + private fun aPaginationState(): Timeline.PaginationState { + return Timeline.PaginationState( + hasMoreToLoad = false, + ) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt new file mode 100644 index 0000000000..68b80c7e8f --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/test/fakes/FakeTimeline.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 The Matrix.org Foundation C.I.C. + * + * 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 + * + * http://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 org.matrix.android.sdk.test.fakes + +import io.mockk.coEvery +import io.mockk.every +import io.mockk.justRun +import io.mockk.mockk +import org.matrix.android.sdk.api.session.room.timeline.Timeline +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent + +class FakeTimeline { + val instance: Timeline = mockk() + + fun givenRestartWithEventIdSuccess(eventId: String) { + justRun { instance.restartWithEventId(eventId) } + } + + fun givenAwaitPaginateReturns(events: List, direction: Timeline.Direction, count: Int) { + coEvery { instance.awaitPaginate(direction, count) } returns events + } + + fun givenGetPaginationStateReturns(paginationState: Timeline.PaginationState, direction: Timeline.Direction) { + every { instance.getPaginationState(direction) } returns paginationState + } +}