diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt index eb7364abd3..e6e169b9b4 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/event/FilterAndStoreEventsTask.kt @@ -58,7 +58,6 @@ internal class DefaultFilterAndStoreEventsTask @Inject constructor( private suspend fun addMissingEventsInDB(roomId: String, events: List) { monarchy.awaitTransaction { realm -> - // TODO we should insert TimelineEventEntity as well, how to do that???? val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } if (eventIdsToCheck.isNotEmpty()) { val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt new file mode 100644 index 0000000000..1b45430fd1 --- /dev/null +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/event/DefaultFilterAndStoreEventsTaskTest.kt @@ -0,0 +1,133 @@ +/* + * 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.event + +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkAll +import io.mockk.verify +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.EventType +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.isPollResponse +import org.matrix.android.sdk.api.session.room.send.SendState +import org.matrix.android.sdk.internal.database.mapper.toEntity +import org.matrix.android.sdk.internal.database.model.EventEntity +import org.matrix.android.sdk.internal.database.model.EventEntityFields +import org.matrix.android.sdk.internal.database.model.EventInsertType +import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse +import org.matrix.android.sdk.internal.session.room.relation.poll.FETCH_RELATED_EVENTS_LIMIT +import org.matrix.android.sdk.internal.session.room.relation.poll.FetchPollResponseEventsTask +import org.matrix.android.sdk.test.fakes.FakeClock +import org.matrix.android.sdk.test.fakes.FakeEventDecryptor +import org.matrix.android.sdk.test.fakes.FakeMonarchy +import org.matrix.android.sdk.test.fakes.givenFindAll +import org.matrix.android.sdk.test.fakes.givenIn + +@OptIn(ExperimentalCoroutinesApi::class) +internal class DefaultFilterAndStoreEventsTaskTest { + + private val fakeMonarchy = FakeMonarchy() + private val fakeClock = FakeClock() + private val fakeEventDecryptor = FakeEventDecryptor() + + private val defaultFilterAndStoreEventsTask = DefaultFilterAndStoreEventsTask( + monarchy = fakeMonarchy.instance, + clock = fakeClock, + eventDecryptor = fakeEventDecryptor.instance, + ) + + @Before + fun setup() { + mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") + mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt") + mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") + } + + @After + fun tearDown() { + unmockkAll() + } + + @Test + fun `given a room and list of events when execute then filter in using given predicate and store them in local if needed`() = runTest { + // Given + val aRoomId = "roomId" + val anEventId1 = "eventId1" + val anEventId2 = "eventId2" + val anEventId3 = "eventId3" + val anEventId4 = "eventId4" + val event1 = givenAnEvent(eventId = anEventId1, isEncrypted = true, clearType = EventType.ENCRYPTED) + val event2 = givenAnEvent(eventId = anEventId2, isEncrypted = true, clearType = EventType.MESSAGE) + val event3 = givenAnEvent(eventId = anEventId3, isEncrypted = false, clearType = EventType.MESSAGE) + val event4 = givenAnEvent(eventId = anEventId4, isEncrypted = false, clearType = EventType.MESSAGE) + val events = listOf(event1, event2, event3, event4) + val filterPredicate = { event: Event -> event == event2 } + val params = givenTaskParams(roomId = aRoomId, events = events, predicate = filterPredicate) + fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1) + fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2) + fakeClock.givenEpoch(123) + givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1)) + val eventEntityToSave = EventEntity(eventId = anEventId2) + every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave + every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave + + // When + defaultFilterAndStoreEventsTask.execute(params) + + // Then + fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "") + fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "") + // Check we save in DB the event2 which is a non stored poll response + verify { + event2.toEntity(aRoomId, SendState.SYNCED, any()) + eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION) + } + } + + private fun givenTaskParams(roomId: String, events: List, predicate: (Event) -> Boolean) = FilterAndStoreEventsTask.Params( + roomId = roomId, + events = events, + filterPredicate = predicate, + ) + + private fun givenAnEvent( + eventId: String, + isEncrypted: Boolean, + clearType: String, + ): Event { + val event = mockk(relaxed = true) + every { event.eventId } returns eventId + every { event.isEncrypted() } returns isEncrypted + every { event.getClearType() } returns clearType + return event + } + + private fun givenExistingEventEntities(eventIdsToCheck: List, existingIds: List) { + val eventEntities = existingIds.map { EventEntity(eventId = it) } + fakeMonarchy.givenWhere() + .givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck) + .givenFindAll(eventEntities) + } +} diff --git a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt index 8d50bac38f..238a4fa626 100644 --- a/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt +++ b/matrix-sdk-android/src/test/java/org/matrix/android/sdk/internal/session/room/relation/poll/DefaultFetchPollResponseEventsTaskTest.kt @@ -16,11 +16,12 @@ package org.matrix.android.sdk.internal.session.room.relation.poll +import io.mockk.coJustRun +import io.mockk.coVerify import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.unmockkAll -import io.mockk.verify import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After @@ -29,41 +30,28 @@ import org.junit.Test import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.isPollResponse -import org.matrix.android.sdk.api.session.room.send.SendState -import org.matrix.android.sdk.internal.database.mapper.toEntity -import org.matrix.android.sdk.internal.database.model.EventEntity -import org.matrix.android.sdk.internal.database.model.EventEntityFields -import org.matrix.android.sdk.internal.database.model.EventInsertType -import org.matrix.android.sdk.internal.database.query.copyToRealmOrIgnore +import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse -import org.matrix.android.sdk.test.fakes.FakeClock -import org.matrix.android.sdk.test.fakes.FakeEventDecryptor import org.matrix.android.sdk.test.fakes.FakeGlobalErrorReceiver -import org.matrix.android.sdk.test.fakes.FakeMonarchy import org.matrix.android.sdk.test.fakes.FakeRoomApi -import org.matrix.android.sdk.test.fakes.givenFindAll -import org.matrix.android.sdk.test.fakes.givenIn @OptIn(ExperimentalCoroutinesApi::class) internal class DefaultFetchPollResponseEventsTaskTest { private val fakeRoomAPI = FakeRoomApi() private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver() - private val fakeMonarchy = FakeMonarchy() - private val fakeClock = FakeClock() - private val fakeEventDecryptor = FakeEventDecryptor() + private val filterAndStoreEventsTask = mockk() private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask( roomAPI = fakeRoomAPI.instance, globalErrorReceiver = fakeGlobalErrorReceiver, - monarchy = fakeMonarchy.instance, - clock = fakeClock, - eventDecryptor = fakeEventDecryptor.instance, + filterAndStoreEventsTask = filterAndStoreEventsTask, ) @Before fun setup() { mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") + mockkStatic("org.matrix.android.sdk.internal.database.mapper.EventMapperKt") mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") } @@ -74,7 +62,7 @@ internal class DefaultFetchPollResponseEventsTaskTest { } @Test - fun `given a room and a poll when execute then fetch related events and store them in local if needed`() = runTest { + fun `given a room and a poll when execute then fetch related events and store them in local`() = runTest { // Given val aRoomId = "roomId" val aPollEventId = "eventId" @@ -94,13 +82,7 @@ internal class DefaultFetchPollResponseEventsTaskTest { fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse) val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null) fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse) - fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1) - fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event2) - fakeClock.givenEpoch(123) - givenExistingEventEntities(eventIdsToCheck = listOf(anEventId1, anEventId2), existingIds = listOf(anEventId1)) - val eventEntityToSave = EventEntity(eventId = anEventId2) - every { event2.toEntity(any(), any(), any()) } returns eventEntityToSave - every { eventEntityToSave.copyToRealmOrIgnore(any(), any()) } returns eventEntityToSave + coJustRun { filterAndStoreEventsTask.execute(any()) } // When defaultFetchPollResponseEventsTask.execute(params) @@ -111,21 +93,22 @@ internal class DefaultFetchPollResponseEventsTaskTest { eventId = params.startPollEventId, relationType = RelationType.REFERENCE, from = null, - limit = FETCH_RELATED_EVENTS_LIMIT + limit = FETCH_RELATED_EVENTS_LIMIT, ) fakeRoomAPI.verifyGetRelations( roomId = params.roomId, eventId = params.startPollEventId, relationType = RelationType.REFERENCE, from = aNextBatchToken, - limit = FETCH_RELATED_EVENTS_LIMIT + limit = FETCH_RELATED_EVENTS_LIMIT, ) - fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "") - fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "") - // Check we save in DB the event2 which is a non stored poll response - verify { - event2.toEntity(aRoomId, SendState.SYNCED, any()) - eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION) + coVerify { + filterAndStoreEventsTask.execute(match { + it.roomId == aRoomId && it.events == firstEvents + }) + filterAndStoreEventsTask.execute(match { + it.roomId == aRoomId && it.events == secondEvents + }) } } @@ -153,11 +136,4 @@ internal class DefaultFetchPollResponseEventsTaskTest { every { event.isEncrypted() } returns isEncrypted return event } - - private fun givenExistingEventEntities(eventIdsToCheck: List, existingIds: List) { - val eventEntities = existingIds.map { EventEntity(eventId = it) } - fakeMonarchy.givenWhere() - .givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck) - .givenFindAll(eventEntities) - } }