Fixing unit tests in SDK

This commit is contained in:
Maxime NATUREL 2023-01-26 13:50:46 +01:00
parent c7d3e1926f
commit 030e37655e
3 changed files with 150 additions and 42 deletions

View File

@ -58,7 +58,6 @@ internal class DefaultFilterAndStoreEventsTask @Inject constructor(
private suspend fun addMissingEventsInDB(roomId: String, events: List<Event>) { private suspend fun addMissingEventsInDB(roomId: String, events: List<Event>) {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
// TODO we should insert TimelineEventEntity as well, how to do that????
val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() } val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() }
if (eventIdsToCheck.isNotEmpty()) { if (eventIdsToCheck.isNotEmpty()) {
val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId } val existingIds = EventEntity.where(realm, eventIdsToCheck).findAll().toList().map { it.eventId }

View File

@ -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<Event>, predicate: (Event) -> Boolean) = FilterAndStoreEventsTask.Params(
roomId = roomId,
events = events,
filterPredicate = predicate,
)
private fun givenAnEvent(
eventId: String,
isEncrypted: Boolean,
clearType: String,
): Event {
val event = mockk<Event>(relaxed = true)
every { event.eventId } returns eventId
every { event.isEncrypted() } returns isEncrypted
every { event.getClearType() } returns clearType
return event
}
private fun givenExistingEventEntities(eventIdsToCheck: List<String>, existingIds: List<String>) {
val eventEntities = existingIds.map { EventEntity(eventId = it) }
fakeMonarchy.givenWhere<EventEntity>()
.givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck)
.givenFindAll(eventEntities)
}
}

View File

@ -16,11 +16,12 @@
package org.matrix.android.sdk.internal.session.room.relation.poll 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.every
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.unmockkAll import io.mockk.unmockkAll
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.After 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.Event
import org.matrix.android.sdk.api.session.events.model.RelationType 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.events.model.isPollResponse
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.internal.session.room.event.FilterAndStoreEventsTask
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.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.FakeGlobalErrorReceiver
import org.matrix.android.sdk.test.fakes.FakeMonarchy
import org.matrix.android.sdk.test.fakes.FakeRoomApi 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) @OptIn(ExperimentalCoroutinesApi::class)
internal class DefaultFetchPollResponseEventsTaskTest { internal class DefaultFetchPollResponseEventsTaskTest {
private val fakeRoomAPI = FakeRoomApi() private val fakeRoomAPI = FakeRoomApi()
private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver() private val fakeGlobalErrorReceiver = FakeGlobalErrorReceiver()
private val fakeMonarchy = FakeMonarchy() private val filterAndStoreEventsTask = mockk<FilterAndStoreEventsTask>()
private val fakeClock = FakeClock()
private val fakeEventDecryptor = FakeEventDecryptor()
private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask( private val defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask(
roomAPI = fakeRoomAPI.instance, roomAPI = fakeRoomAPI.instance,
globalErrorReceiver = fakeGlobalErrorReceiver, globalErrorReceiver = fakeGlobalErrorReceiver,
monarchy = fakeMonarchy.instance, filterAndStoreEventsTask = filterAndStoreEventsTask,
clock = fakeClock,
eventDecryptor = fakeEventDecryptor.instance,
) )
@Before @Before
fun setup() { fun setup() {
mockkStatic("org.matrix.android.sdk.api.session.events.model.EventKt") 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.mapper.EventMapperKt")
mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt") mockkStatic("org.matrix.android.sdk.internal.database.query.EventEntityQueriesKt")
} }
@ -74,7 +62,7 @@ internal class DefaultFetchPollResponseEventsTaskTest {
} }
@Test @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 // Given
val aRoomId = "roomId" val aRoomId = "roomId"
val aPollEventId = "eventId" val aPollEventId = "eventId"
@ -94,13 +82,7 @@ internal class DefaultFetchPollResponseEventsTaskTest {
fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse) fakeRoomAPI.givenGetRelationsReturns(from = null, relationsResponse = firstResponse)
val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null) val secondResponse = givenARelationsResponse(events = secondEvents, nextBatch = null)
fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse) fakeRoomAPI.givenGetRelationsReturns(from = aNextBatchToken, relationsResponse = secondResponse)
fakeEventDecryptor.givenDecryptEventAndSaveResultSuccess(event1) coJustRun { filterAndStoreEventsTask.execute(any()) }
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 // When
defaultFetchPollResponseEventsTask.execute(params) defaultFetchPollResponseEventsTask.execute(params)
@ -111,21 +93,22 @@ internal class DefaultFetchPollResponseEventsTaskTest {
eventId = params.startPollEventId, eventId = params.startPollEventId,
relationType = RelationType.REFERENCE, relationType = RelationType.REFERENCE,
from = null, from = null,
limit = FETCH_RELATED_EVENTS_LIMIT limit = FETCH_RELATED_EVENTS_LIMIT,
) )
fakeRoomAPI.verifyGetRelations( fakeRoomAPI.verifyGetRelations(
roomId = params.roomId, roomId = params.roomId,
eventId = params.startPollEventId, eventId = params.startPollEventId,
relationType = RelationType.REFERENCE, relationType = RelationType.REFERENCE,
from = aNextBatchToken, from = aNextBatchToken,
limit = FETCH_RELATED_EVENTS_LIMIT limit = FETCH_RELATED_EVENTS_LIMIT,
) )
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event1, timeline = "") coVerify {
fakeEventDecryptor.verifyDecryptEventAndSaveResult(event2, timeline = "") filterAndStoreEventsTask.execute(match {
// Check we save in DB the event2 which is a non stored poll response it.roomId == aRoomId && it.events == firstEvents
verify { })
event2.toEntity(aRoomId, SendState.SYNCED, any()) filterAndStoreEventsTask.execute(match {
eventEntityToSave.copyToRealmOrIgnore(fakeMonarchy.fakeRealm.instance, EventInsertType.PAGINATION) it.roomId == aRoomId && it.events == secondEvents
})
} }
} }
@ -153,11 +136,4 @@ internal class DefaultFetchPollResponseEventsTaskTest {
every { event.isEncrypted() } returns isEncrypted every { event.isEncrypted() } returns isEncrypted
return event return event
} }
private fun givenExistingEventEntities(eventIdsToCheck: List<String>, existingIds: List<String>) {
val eventEntities = existingIds.map { EventEntity(eventId = it) }
fakeMonarchy.givenWhere<EventEntity>()
.givenIn(EventEntityFields.EVENT_ID, eventIdsToCheck)
.givenFindAll(eventEntities)
}
} }