Merge pull request #6267 from vector-im/feature/mna/6155-tests-lls-aggregation
Adding unit tests for live location sharing aggregation code (PSF-1063)
This commit is contained in:
commit
539d134b77
|
@ -0,0 +1 @@
|
||||||
|
Add unit tests for LiveLocationAggregationProcessor code
|
|
@ -84,6 +84,7 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.findActiveLiveIn
|
||||||
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
.equalTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
||||||
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
|
.notEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, ignoredEventId)
|
||||||
.findAll()
|
.findAll()
|
||||||
|
.toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -36,16 +36,22 @@ import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
// TODO add unit tests
|
/**
|
||||||
|
* Aggregates all live location sharing related events in local database.
|
||||||
|
*/
|
||||||
internal class LiveLocationAggregationProcessor @Inject constructor(
|
internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val clock: Clock,
|
private val clock: Clock,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
|
/**
|
||||||
|
* Handle the content of a beacon info.
|
||||||
|
* @return true if it has been processed, false if ignored.
|
||||||
|
*/
|
||||||
|
fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean): Boolean {
|
||||||
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val isLive = content.isLive.orTrue()
|
val isLive = content.isLive.orTrue()
|
||||||
|
@ -58,7 +64,7 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
|
|
||||||
if (targetEventId.isNullOrEmpty()) {
|
if (targetEventId.isNullOrEmpty()) {
|
||||||
Timber.w("no target event id found for the beacon content")
|
Timber.w("no target event id found for the beacon content")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
|
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
|
||||||
|
@ -83,6 +89,8 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
cancelDeactivationAfterTimeout(targetEventId, roomId)
|
cancelDeactivationAfterTimeout(targetEventId, roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) {
|
private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) {
|
||||||
|
@ -110,6 +118,10 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
workManagerProvider.workManager.cancelUniqueWork(workName)
|
workManagerProvider.workManager.cancelUniqueWork(workName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the content of a beacon location data.
|
||||||
|
* @return true if it has been processed, false if ignored.
|
||||||
|
*/
|
||||||
fun handleBeaconLocationData(
|
fun handleBeaconLocationData(
|
||||||
realm: Realm,
|
realm: Realm,
|
||||||
event: Event,
|
event: Event,
|
||||||
|
@ -117,14 +129,14 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
relatedEventId: String?,
|
relatedEventId: String?,
|
||||||
isLocalEcho: Boolean
|
isLocalEcho: Boolean
|
||||||
) {
|
): Boolean {
|
||||||
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relatedEventId.isNullOrEmpty()) {
|
if (relatedEventId.isNullOrEmpty()) {
|
||||||
Timber.w("no related event id found for the live location content")
|
Timber.w("no related event id found for the live location content")
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
|
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.getOrCreate(
|
||||||
|
@ -139,9 +151,12 @@ internal class LiveLocationAggregationProcessor @Inject constructor(
|
||||||
?.getBestTimestampMillis()
|
?.getBestTimestampMillis()
|
||||||
?: 0
|
?: 0
|
||||||
|
|
||||||
if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
|
return if (updatedLocationTimestamp.isMoreRecentThan(currentLocationTimestamp)) {
|
||||||
Timber.d("updating last location of the summary of id=$relatedEventId")
|
Timber.d("updating last location of the summary of id=$relatedEventId")
|
||||||
aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
|
aggregatedSummary.lastLocationContent = ContentMapper.map(content.toContent())
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,405 @@
|
||||||
|
/*
|
||||||
|
* 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.aggregation.livelocation
|
||||||
|
|
||||||
|
import androidx.work.ExistingWorkPolicy
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.UnsignedData
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.LocationInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconInfoContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocationDataContent
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntityFields
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeClock
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeWorkManagerProvider
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindAll
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenNotEqualTo
|
||||||
|
|
||||||
|
private const val A_SESSION_ID = "session_id"
|
||||||
|
private const val A_SENDER_ID = "sender_id"
|
||||||
|
private const val AN_EVENT_ID = "event_id"
|
||||||
|
private const val A_ROOM_ID = "room_id"
|
||||||
|
private const val A_TIMESTAMP = 1654689143L
|
||||||
|
private const val A_TIMEOUT_MILLIS = 15 * 60 * 1000L
|
||||||
|
private const val A_LATITUDE = 40.05
|
||||||
|
private const val A_LONGITUDE = 29.24
|
||||||
|
private const val A_UNCERTAINTY = 30.0
|
||||||
|
private const val A_GEO_URI = "geo:$A_LATITUDE,$A_LONGITUDE;$A_UNCERTAINTY"
|
||||||
|
|
||||||
|
internal class LiveLocationAggregationProcessorTest {
|
||||||
|
|
||||||
|
private val fakeWorkManagerProvider = FakeWorkManagerProvider()
|
||||||
|
private val fakeClock = FakeClock()
|
||||||
|
private val fakeRealm = FakeRealm()
|
||||||
|
private val fakeQuery = fakeRealm.givenWhere<LiveLocationShareAggregatedSummaryEntity>()
|
||||||
|
|
||||||
|
private val liveLocationAggregationProcessor = LiveLocationAggregationProcessor(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
workManagerProvider = fakeWorkManagerProvider.instance,
|
||||||
|
clock = fakeClock
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon info when it is local echo then it is ignored`() {
|
||||||
|
val event = Event(senderId = A_SENDER_ID)
|
||||||
|
val beaconInfo = MessageBeaconInfoContent()
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconInfo(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconInfo,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
isLocalEcho = true
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class IgnoredBeaconInfoEvent(
|
||||||
|
val event: Event,
|
||||||
|
val beaconInfo: MessageBeaconInfoContent
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon info and event when some values are missing then it is ignored`() {
|
||||||
|
val ignoredInfoEvents = listOf(
|
||||||
|
// missing senderId
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(eventId = AN_EVENT_ID, senderId = null),
|
||||||
|
beaconInfo = MessageBeaconInfoContent()
|
||||||
|
),
|
||||||
|
// empty senderId
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(eventId = AN_EVENT_ID, senderId = ""),
|
||||||
|
beaconInfo = MessageBeaconInfoContent()
|
||||||
|
),
|
||||||
|
// beacon is live and no eventId
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(eventId = null, senderId = A_SENDER_ID),
|
||||||
|
beaconInfo = MessageBeaconInfoContent(isLive = true)
|
||||||
|
),
|
||||||
|
// beacon is live and eventId is empty
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(eventId = "", senderId = A_SENDER_ID),
|
||||||
|
beaconInfo = MessageBeaconInfoContent(isLive = true)
|
||||||
|
),
|
||||||
|
// beacon is not live and replaced event id is null
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
senderId = A_SENDER_ID,
|
||||||
|
unsignedData = UnsignedData(
|
||||||
|
age = 123,
|
||||||
|
replacesState = null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
beaconInfo = MessageBeaconInfoContent(isLive = false)
|
||||||
|
),
|
||||||
|
// beacon is not live and replaced event id is empty
|
||||||
|
IgnoredBeaconInfoEvent(
|
||||||
|
event = Event(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
senderId = A_SENDER_ID,
|
||||||
|
unsignedData = UnsignedData(
|
||||||
|
age = 123,
|
||||||
|
replacesState = ""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
beaconInfo = MessageBeaconInfoContent(isLive = false)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ignoredInfoEvents.forEach {
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconInfo(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = it.event,
|
||||||
|
content = it.beaconInfo,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon info and existing entity when beacon content is correct and active then it is aggregated`() {
|
||||||
|
val event = Event(
|
||||||
|
senderId = A_SENDER_ID,
|
||||||
|
eventId = AN_EVENT_ID
|
||||||
|
)
|
||||||
|
val beaconInfo = MessageBeaconInfoContent(
|
||||||
|
isLive = true,
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP,
|
||||||
|
timeout = A_TIMEOUT_MILLIS
|
||||||
|
)
|
||||||
|
fakeClock.givenEpoch(A_TIMESTAMP + 5000)
|
||||||
|
fakeWorkManagerProvider.fakeWorkManager.expectEnqueueUniqueWork()
|
||||||
|
val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||||
|
val previousEntities = givenActiveSummaryListQueryReturns(
|
||||||
|
listOf(
|
||||||
|
LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
eventId = "${AN_EVENT_ID}1",
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
userId = A_SENDER_ID,
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconInfo(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconInfo,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo true
|
||||||
|
aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID
|
||||||
|
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
||||||
|
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
||||||
|
aggregatedEntity.isActive shouldBeEqualTo true
|
||||||
|
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
||||||
|
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
||||||
|
previousEntities.forEach { entity ->
|
||||||
|
entity.isActive shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
fakeWorkManagerProvider.fakeWorkManager.verifyEnqueueUniqueWork(
|
||||||
|
workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID),
|
||||||
|
policy = ExistingWorkPolicy.REPLACE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon info and existing entity when beacon content is correct and inactive then it is aggregated`() {
|
||||||
|
val unsignedData = UnsignedData(
|
||||||
|
age = 123,
|
||||||
|
replacesState = AN_EVENT_ID
|
||||||
|
)
|
||||||
|
val event = Event(
|
||||||
|
senderId = A_SENDER_ID,
|
||||||
|
eventId = "",
|
||||||
|
unsignedData = unsignedData
|
||||||
|
)
|
||||||
|
val beaconInfo = MessageBeaconInfoContent(
|
||||||
|
isLive = false,
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP,
|
||||||
|
timeout = A_TIMEOUT_MILLIS
|
||||||
|
)
|
||||||
|
fakeClock.givenEpoch(A_TIMESTAMP + 5000)
|
||||||
|
fakeWorkManagerProvider.fakeWorkManager.expectCancelUniqueWork()
|
||||||
|
val aggregatedEntity = givenLastSummaryQueryReturns(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||||
|
val previousEntities = givenActiveSummaryListQueryReturns(
|
||||||
|
listOf(
|
||||||
|
LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
eventId = "${AN_EVENT_ID}1",
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
userId = A_SENDER_ID,
|
||||||
|
isActive = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconInfo(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconInfo,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo true
|
||||||
|
aggregatedEntity.eventId shouldBeEqualTo AN_EVENT_ID
|
||||||
|
aggregatedEntity.roomId shouldBeEqualTo A_ROOM_ID
|
||||||
|
aggregatedEntity.userId shouldBeEqualTo A_SENDER_ID
|
||||||
|
aggregatedEntity.isActive shouldBeEqualTo false
|
||||||
|
aggregatedEntity.endOfLiveTimestampMillis shouldBeEqualTo A_TIMESTAMP + A_TIMEOUT_MILLIS
|
||||||
|
aggregatedEntity.lastLocationContent shouldBeEqualTo null
|
||||||
|
previousEntities.forEach { entity ->
|
||||||
|
entity.isActive shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
fakeWorkManagerProvider.fakeWorkManager.verifyCancelUniqueWork(
|
||||||
|
workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = AN_EVENT_ID, roomId = A_ROOM_ID)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon location data when it is local echo then it is ignored`() {
|
||||||
|
val event = Event(senderId = A_SENDER_ID)
|
||||||
|
val beaconLocationData = MessageBeaconLocationDataContent()
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconLocationData,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
relatedEventId = AN_EVENT_ID,
|
||||||
|
isLocalEcho = true
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class IgnoredBeaconLocationDataEvent(
|
||||||
|
val event: Event,
|
||||||
|
val beaconLocationData: MessageBeaconLocationDataContent
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given event and beacon location data when some values are missing then it is ignored`() {
|
||||||
|
val ignoredLocationDataEvents = listOf(
|
||||||
|
// missing sender id
|
||||||
|
IgnoredBeaconLocationDataEvent(
|
||||||
|
event = Event(eventId = AN_EVENT_ID),
|
||||||
|
beaconLocationData = MessageBeaconLocationDataContent()
|
||||||
|
),
|
||||||
|
// empty sender id
|
||||||
|
IgnoredBeaconLocationDataEvent(
|
||||||
|
event = Event(eventId = AN_EVENT_ID, senderId = ""),
|
||||||
|
beaconLocationData = MessageBeaconLocationDataContent()
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ignoredLocationDataEvents.forEach {
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = it.event,
|
||||||
|
content = it.beaconLocationData,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
relatedEventId = "",
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon location data when relatedEventId is null or empty then it is ignored`() {
|
||||||
|
val event = Event(senderId = A_SENDER_ID)
|
||||||
|
val beaconLocationData = MessageBeaconLocationDataContent()
|
||||||
|
|
||||||
|
listOf(null, "").forEach {
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconLocationData,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
relatedEventId = it,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon location data when location is less recent than the saved one then it is ignored`() {
|
||||||
|
val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID)
|
||||||
|
val beaconLocationData = MessageBeaconLocationDataContent(
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP - 60_000
|
||||||
|
)
|
||||||
|
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP
|
||||||
|
)
|
||||||
|
givenLastSummaryQueryReturns(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
beaconLocationContent = lastBeaconLocationContent
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconLocationData,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
relatedEventId = AN_EVENT_ID,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo false
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given beacon location data when location is more recent than the saved one then it is aggregated`() {
|
||||||
|
val event = Event(eventId = AN_EVENT_ID, senderId = A_SENDER_ID)
|
||||||
|
val locationInfo = LocationInfo(geoUri = A_GEO_URI)
|
||||||
|
val beaconLocationData = MessageBeaconLocationDataContent(
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP,
|
||||||
|
unstableLocationInfo = locationInfo
|
||||||
|
)
|
||||||
|
val lastBeaconLocationContent = MessageBeaconLocationDataContent(
|
||||||
|
unstableTimestampMillis = A_TIMESTAMP - 60_000
|
||||||
|
)
|
||||||
|
val entity = givenLastSummaryQueryReturns(
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
beaconLocationContent = lastBeaconLocationContent
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = liveLocationAggregationProcessor.handleBeaconLocationData(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = event,
|
||||||
|
content = beaconLocationData,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
relatedEventId = AN_EVENT_ID,
|
||||||
|
isLocalEcho = false
|
||||||
|
)
|
||||||
|
|
||||||
|
result shouldBeEqualTo true
|
||||||
|
val savedLocationData = ContentMapper.map(entity.lastLocationContent).toModel<MessageBeaconLocationDataContent>()
|
||||||
|
savedLocationData?.getBestTimestampMillis() shouldBeEqualTo A_TIMESTAMP
|
||||||
|
savedLocationData?.getBestLocationInfo()?.geoUri shouldBeEqualTo A_GEO_URI
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenLastSummaryQueryReturns(
|
||||||
|
eventId: String,
|
||||||
|
roomId: String,
|
||||||
|
beaconLocationContent: MessageBeaconLocationDataContent? = null
|
||||||
|
): LiveLocationShareAggregatedSummaryEntity {
|
||||||
|
val result = LiveLocationShareAggregatedSummaryEntity(
|
||||||
|
eventId = eventId,
|
||||||
|
roomId = roomId,
|
||||||
|
lastLocationContent = ContentMapper.map(beaconLocationContent?.toContent())
|
||||||
|
)
|
||||||
|
fakeQuery
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, eventId)
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, roomId)
|
||||||
|
.givenFindFirst(result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenActiveSummaryListQueryReturns(
|
||||||
|
summaryList: List<LiveLocationShareAggregatedSummaryEntity>
|
||||||
|
): List<LiveLocationShareAggregatedSummaryEntity> {
|
||||||
|
fakeQuery
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.ROOM_ID, A_ROOM_ID)
|
||||||
|
.givenNotEqualTo(LiveLocationShareAggregatedSummaryEntityFields.EVENT_ID, AN_EVENT_ID)
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.USER_ID, A_SENDER_ID)
|
||||||
|
.givenEqualTo(LiveLocationShareAggregatedSummaryEntityFields.IS_ACTIVE, true)
|
||||||
|
.givenFindAll(summaryList)
|
||||||
|
return summaryList
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,8 +19,6 @@ package org.matrix.android.sdk.internal.session.room.aggregation.poll
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.realm.RealmList
|
import io.realm.RealmList
|
||||||
import io.realm.RealmModel
|
|
||||||
import io.realm.RealmQuery
|
|
||||||
import org.amshove.kluent.shouldBeFalse
|
import org.amshove.kluent.shouldBeFalse
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
|
@ -46,6 +44,8 @@ import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsT
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_TIMELINE_EVENT
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollEventsTestData.A_USER_ID_1
|
||||||
import org.matrix.android.sdk.test.fakes.FakeRealm
|
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||||
|
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||||
|
|
||||||
class PollAggregationProcessorTest {
|
class PollAggregationProcessorTest {
|
||||||
|
|
||||||
|
@ -135,14 +135,11 @@ class PollAggregationProcessorTest {
|
||||||
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse()
|
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, event).shouldBeFalse()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(fieldName: String, value: String, result: RealmQuery<T>) {
|
|
||||||
every { equalTo(fieldName, value) } returns result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mockEventAnnotationsSummaryEntity() {
|
private fun mockEventAnnotationsSummaryEntity() {
|
||||||
val queryResult = realm.givenWhereReturns(result = EventAnnotationsSummaryEntity())
|
realm.givenWhere<EventAnnotationsSummaryEntity>()
|
||||||
queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!, queryResult)
|
.givenFindFirst(EventAnnotationsSummaryEntity())
|
||||||
queryResult.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!, queryResult)
|
.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, A_POLL_REPLACE_EVENT.roomId!!)
|
||||||
|
.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, A_POLL_REPLACE_EVENT.eventId!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mockRoom(
|
private fun mockRoom(
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* 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.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.internal.util.time.Clock
|
||||||
|
|
||||||
|
internal class FakeClock : Clock by mockk() {
|
||||||
|
fun givenEpoch(epoch: Long) {
|
||||||
|
every { epochMillis() } returns epoch
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,16 +21,59 @@ import io.mockk.mockk
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
import io.realm.RealmModel
|
import io.realm.RealmModel
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
import io.realm.RealmResults
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
|
||||||
internal class FakeRealm {
|
internal class FakeRealm {
|
||||||
|
|
||||||
val instance = mockk<Realm>(relaxed = true)
|
val instance = mockk<Realm>(relaxed = true)
|
||||||
|
|
||||||
inline fun <reified T : RealmModel> givenWhereReturns(result: T?): RealmQuery<T> {
|
inline fun <reified T : RealmModel> givenWhere(): RealmQuery<T> {
|
||||||
val queryResult = mockk<RealmQuery<T>>()
|
val query = mockk<RealmQuery<T>>()
|
||||||
every { queryResult.findFirst() } returns result
|
every { instance.where<T>() } returns query
|
||||||
every { instance.where<T>() } returns queryResult
|
return query
|
||||||
return queryResult
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenFindFirst(
|
||||||
|
result: T?
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { findFirst() } returns result
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenFindAll(
|
||||||
|
result: List<T>
|
||||||
|
): RealmQuery<T> {
|
||||||
|
val realmResults = mockk<RealmResults<T>>()
|
||||||
|
result.forEachIndexed { index, t ->
|
||||||
|
every { realmResults[index] } returns t
|
||||||
|
}
|
||||||
|
every { realmResults.size } returns result.size
|
||||||
|
every { findAll() } returns realmResults
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(
|
||||||
|
fieldName: String,
|
||||||
|
value: String
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { equalTo(fieldName, value) } returns this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenEqualTo(
|
||||||
|
fieldName: String,
|
||||||
|
value: Boolean
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { equalTo(fieldName, value) } returns this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenNotEqualTo(
|
||||||
|
fieldName: String,
|
||||||
|
value: String
|
||||||
|
): RealmQuery<T> {
|
||||||
|
every { notEqualTo(fieldName, value) } returns this
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 androidx.work.ExistingWorkPolicy
|
||||||
|
import androidx.work.OneTimeWorkRequest
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
|
||||||
|
class FakeWorkManager {
|
||||||
|
|
||||||
|
val instance = mockk<WorkManager>()
|
||||||
|
|
||||||
|
fun expectEnqueueUniqueWork() {
|
||||||
|
every { instance.enqueueUniqueWork(any(), any(), any<OneTimeWorkRequest>()) } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyEnqueueUniqueWork(workName: String, policy: ExistingWorkPolicy) {
|
||||||
|
verify { instance.enqueueUniqueWork(workName, policy, any<OneTimeWorkRequest>()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expectCancelUniqueWork() {
|
||||||
|
every { instance.cancelUniqueWork(any()) } returns mockk()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifyCancelUniqueWork(workName: String) {
|
||||||
|
verify { instance.cancelUniqueWork(workName) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
|
|
||||||
|
internal class FakeWorkManagerProvider(
|
||||||
|
val fakeWorkManager: FakeWorkManager = FakeWorkManager(),
|
||||||
|
) {
|
||||||
|
|
||||||
|
val instance = mockk<WorkManagerProvider>().also {
|
||||||
|
every { it.workManager } returns fakeWorkManager.instance
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue