Adding unit test on task to fetch the poll response events
This commit is contained in:
parent
644803dcf3
commit
bd7b6d6495
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022 New Vector Ltd
|
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.room.relation.poll
|
package org.matrix.android.sdk.internal.session.room.relation.poll
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
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
|
||||||
|
@ -37,7 +38,8 @@ import org.matrix.android.sdk.internal.util.awaitTransaction
|
||||||
import org.matrix.android.sdk.internal.util.time.Clock
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
private const val FETCH_RELATED_EVENTS_LIMIT = 50
|
@VisibleForTesting
|
||||||
|
const val FETCH_RELATED_EVENTS_LIMIT = 50
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task to fetch all the vote events to ensure full aggregation for a given poll.
|
* Task to fetch all the vote events to ensure full aggregation for a given poll.
|
||||||
|
@ -49,7 +51,6 @@ internal interface FetchPollResponseEventsTask : Task<FetchPollResponseEventsTas
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add unit tests
|
|
||||||
internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
internal class DefaultFetchPollResponseEventsTask @Inject constructor(
|
||||||
private val roomAPI: RoomAPI,
|
private val roomAPI: RoomAPI,
|
||||||
private val globalErrorReceiver: GlobalErrorReceiver,
|
private val globalErrorReceiver: GlobalErrorReceiver,
|
||||||
|
@ -93,24 +94,25 @@ internal class DefaultFetchPollResponseEventsTask @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 ->
|
||||||
val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.isNotEmpty() }
|
val eventIdsToCheck = events.mapNotNull { it.eventId }.filter { it.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 }
|
||||||
|
|
||||||
events.filterNot { it.eventId in existingIds }
|
events.filterNot { it.eventId in existingIds }
|
||||||
.map {
|
.map { it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = computeLocalTs(it)) }
|
||||||
val ageLocalTs = clock.epochMillis() - (it.unsignedData?.age ?: 0)
|
|
||||||
it.toEntity(roomId = roomId, sendState = SendState.SYNCED, ageLocalTs = ageLocalTs)
|
|
||||||
}
|
|
||||||
.forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) }
|
.forEach { it.copyToRealmOrIgnore(realm, EventInsertType.PAGINATION) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun decryptEventIfNeeded(event: Event): Event {
|
private suspend fun decryptEventIfNeeded(event: Event): Event {
|
||||||
if (event.isEncrypted()) {
|
if (event.isEncrypted()) {
|
||||||
eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
|
eventDecryptor.decryptEventAndSaveResult(event, timeline = "")
|
||||||
}
|
}
|
||||||
|
|
||||||
event.ageLocalTs = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
event.ageLocalTs = computeLocalTs(event)
|
||||||
|
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun computeLocalTs(event: Event) = clock.epochMillis() - (event.unsignedData?.age ?: 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.relation.poll
|
||||||
|
|
||||||
|
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.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.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 defaultFetchPollResponseEventsTask = DefaultFetchPollResponseEventsTask(
|
||||||
|
roomAPI = fakeRoomAPI.instance,
|
||||||
|
globalErrorReceiver = fakeGlobalErrorReceiver,
|
||||||
|
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 a poll when execute then fetch related events and store them in local if needed`() = runTest {
|
||||||
|
// Given
|
||||||
|
val aRoomId = "roomId"
|
||||||
|
val aPollEventId = "eventId"
|
||||||
|
val params = givenTaskParams(roomId = aRoomId, eventId = aPollEventId)
|
||||||
|
val aNextBatchToken = "nextBatch"
|
||||||
|
val anEventId1 = "eventId1"
|
||||||
|
val anEventId2 = "eventId2"
|
||||||
|
val anEventId3 = "eventId3"
|
||||||
|
val anEventId4 = "eventId4"
|
||||||
|
val event1 = givenAnEvent(eventId = anEventId1, isPollResponse = true, isEncrypted = true)
|
||||||
|
val event2 = givenAnEvent(eventId = anEventId2, isPollResponse = true, isEncrypted = true)
|
||||||
|
val event3 = givenAnEvent(eventId = anEventId3, isPollResponse = false, isEncrypted = false)
|
||||||
|
val event4 = givenAnEvent(eventId = anEventId4, isPollResponse = false, isEncrypted = false)
|
||||||
|
val firstEvents = listOf(event1, event2)
|
||||||
|
val secondEvents = listOf(event3, event4)
|
||||||
|
val firstResponse = givenARelationsResponse(events = firstEvents, nextBatch = aNextBatchToken)
|
||||||
|
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
|
||||||
|
|
||||||
|
// When
|
||||||
|
defaultFetchPollResponseEventsTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
fakeRoomAPI.verifyGetRelations(
|
||||||
|
roomId = params.roomId,
|
||||||
|
eventId = params.startPollEventId,
|
||||||
|
relationType = RelationType.REFERENCE,
|
||||||
|
from = null,
|
||||||
|
limit = FETCH_RELATED_EVENTS_LIMIT
|
||||||
|
)
|
||||||
|
fakeRoomAPI.verifyGetRelations(
|
||||||
|
roomId = params.roomId,
|
||||||
|
eventId = params.startPollEventId,
|
||||||
|
relationType = RelationType.REFERENCE,
|
||||||
|
from = aNextBatchToken,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenTaskParams(roomId: String, eventId: String) = FetchPollResponseEventsTask.Params(
|
||||||
|
roomId = roomId,
|
||||||
|
startPollEventId = eventId,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun givenARelationsResponse(events: List<Event>, nextBatch: String?): RelationsResponse {
|
||||||
|
return RelationsResponse(
|
||||||
|
chunks = events,
|
||||||
|
nextBatch = nextBatch,
|
||||||
|
prevBatch = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAnEvent(
|
||||||
|
eventId: String,
|
||||||
|
isPollResponse: Boolean,
|
||||||
|
isEncrypted: Boolean,
|
||||||
|
): Event {
|
||||||
|
val event = mockk<Event>(relaxed = true)
|
||||||
|
every { event.eventId } returns eventId
|
||||||
|
every { event.isPollResponse() } returns isPollResponse
|
||||||
|
every { event.isEncrypted() } returns isEncrypted
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.coJustRun
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.internal.crypto.EventDecryptor
|
||||||
|
|
||||||
|
internal class FakeEventDecryptor {
|
||||||
|
val instance: EventDecryptor = mockk()
|
||||||
|
|
||||||
|
fun givenDecryptEventAndSaveResultSuccess(event: Event) {
|
||||||
|
coJustRun { instance.decryptEventAndSaveResult(event, any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyDecryptEventAndSaveResult(event: Event, timeline: String) {
|
||||||
|
coVerify { instance.decryptEventAndSaveResult(event, timeline) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,6 +109,14 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenLessThan(
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenIn(
|
||||||
|
fieldName: String,
|
||||||
|
values: List<String>,
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { `in`(fieldName, values.toTypedArray()) } returns this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked.
|
* Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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.coVerify
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.session.room.relation.RelationsResponse
|
||||||
|
|
||||||
|
internal class FakeRoomApi {
|
||||||
|
|
||||||
|
val instance: RoomAPI = mockk()
|
||||||
|
|
||||||
|
fun givenGetRelationsReturns(
|
||||||
|
from: String?,
|
||||||
|
relationsResponse: RelationsResponse,
|
||||||
|
) {
|
||||||
|
coEvery {
|
||||||
|
instance.getRelations(
|
||||||
|
roomId = any(),
|
||||||
|
eventId = any(),
|
||||||
|
relationType = any(),
|
||||||
|
from = from,
|
||||||
|
limit = any()
|
||||||
|
)
|
||||||
|
} returns relationsResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyGetRelations(
|
||||||
|
roomId: String,
|
||||||
|
eventId: String,
|
||||||
|
relationType: String,
|
||||||
|
from: String?,
|
||||||
|
limit: Int,
|
||||||
|
) {
|
||||||
|
coVerify {
|
||||||
|
instance.getRelations(
|
||||||
|
roomId = roomId,
|
||||||
|
eventId = eventId,
|
||||||
|
relationType = relationType,
|
||||||
|
from = from,
|
||||||
|
limit = limit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue