mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 20:29:10 +03:00
Merge pull request #7851 from vector-im/feature/mna/poll-message-decryption-error
[Poll] Warning message on decryption failure of some events (PSG-1025)
This commit is contained in:
commit
6b98b3023e
28 changed files with 746 additions and 75 deletions
1
changelog.d/7824.feature
Normal file
1
changelog.d/7824.feature
Normal file
|
@ -0,0 +1 @@
|
|||
[Poll] Warning message on decryption failure of some events
|
|
@ -3196,6 +3196,7 @@
|
|||
<string name="closed_poll_option_title">Closed poll</string>
|
||||
<string name="closed_poll_option_description">Results are only revealed when you end the poll</string>
|
||||
<string name="ended_poll_indicator">Ended the poll.</string>
|
||||
<string name="unable_to_decrypt_some_events_in_poll">Due to decryption errors, some votes may not be counted</string>
|
||||
<string name="room_polls_active">Active polls</string>
|
||||
<string name="room_polls_active_no_item">There are no active polls in this room</string>
|
||||
<string name="room_polls_ended">Past polls</string>
|
||||
|
|
|
@ -23,5 +23,7 @@ data class PollResponseAggregatedSummary(
|
|||
val nbOptions: Int = 0,
|
||||
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||
val sourceEvents: List<String>,
|
||||
val localEchos: List<String>
|
||||
val localEchos: List<String>,
|
||||
// list of related event ids which are encrypted due to decryption failure
|
||||
val encryptedRelatedEventIds: List<String>,
|
||||
)
|
||||
|
|
|
@ -59,7 +59,7 @@ internal class EventDecryptor @Inject constructor(
|
|||
private val sendToDeviceTask: SendToDeviceTask,
|
||||
private val deviceListManager: DeviceListManager,
|
||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||
private val cryptoStore: IMXCryptoStore
|
||||
private val cryptoStore: IMXCryptoStore,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,11 +17,16 @@
|
|||
package org.matrix.android.sdk.internal.database
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.RealmResults
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
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.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
import org.matrix.android.sdk.internal.database.mapper.asDomain
|
||||
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventInsertEntity
|
||||
|
@ -34,7 +39,7 @@ import javax.inject.Inject
|
|||
|
||||
internal class EventInsertLiveObserver @Inject constructor(
|
||||
@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>
|
||||
private val processors: Set<@JvmSuppressWildcards EventInsertLiveProcessor>,
|
||||
) :
|
||||
RealmLiveEntityObserver<EventInsertEntity>(realmConfiguration) {
|
||||
|
||||
|
@ -50,48 +55,90 @@ internal class EventInsertLiveObserver @Inject constructor(
|
|||
if (!results.isLoaded || results.isEmpty()) {
|
||||
return@withLock
|
||||
}
|
||||
val idsToDeleteAfterProcess = ArrayList<String>()
|
||||
val filteredEvents = ArrayList<EventInsertEntity>(results.size)
|
||||
val eventsToProcess = ArrayList<EventInsertEntity>(results.size)
|
||||
val eventsToIgnore = ArrayList<EventInsertEntity>(results.size)
|
||||
|
||||
Timber.v("EventInsertEntity updated with ${results.size} results in db")
|
||||
results.forEach {
|
||||
if (shouldProcess(it)) {
|
||||
// don't use copy from realm over there
|
||||
val copiedEvent = EventInsertEntity(
|
||||
eventId = it.eventId,
|
||||
eventType = it.eventType
|
||||
).apply {
|
||||
insertType = it.insertType
|
||||
}
|
||||
filteredEvents.add(copiedEvent)
|
||||
// don't use copy from realm over there
|
||||
val copiedEvent = EventInsertEntity(
|
||||
eventId = it.eventId,
|
||||
eventType = it.eventType
|
||||
).apply {
|
||||
insertType = it.insertType
|
||||
}
|
||||
|
||||
if (shouldProcess(it)) {
|
||||
eventsToProcess.add(copiedEvent)
|
||||
} else {
|
||||
eventsToIgnore.add(copiedEvent)
|
||||
}
|
||||
idsToDeleteAfterProcess.add(it.eventId)
|
||||
}
|
||||
|
||||
awaitTransaction(realmConfiguration) { realm ->
|
||||
Timber.v("##Transaction: There are ${filteredEvents.size} events to process ")
|
||||
filteredEvents.forEach { eventInsert ->
|
||||
Timber.v("##Transaction: There are ${eventsToProcess.size} events to process")
|
||||
|
||||
val idsToDeleteAfterProcess = ArrayList<String>()
|
||||
val idsOfEncryptedEvents = ArrayList<String>()
|
||||
val getAndTriageEvent: (EventInsertEntity) -> Event? = { eventInsert ->
|
||||
val eventId = eventInsert.eventId
|
||||
val event = EventEntity.where(realm, eventId).findFirst()
|
||||
if (event == null) {
|
||||
Timber.v("Event $eventId not found")
|
||||
val event = getEvent(realm, eventId)
|
||||
if (event?.getClearType() == EventType.ENCRYPTED) {
|
||||
idsOfEncryptedEvents.add(eventId)
|
||||
} else {
|
||||
idsToDeleteAfterProcess.add(eventId)
|
||||
}
|
||||
event
|
||||
}
|
||||
|
||||
eventsToProcess.forEach { eventInsert ->
|
||||
val eventId = eventInsert.eventId
|
||||
val event = getAndTriageEvent(eventInsert)
|
||||
|
||||
if (event != null && canProcessEvent(event)) {
|
||||
processors.filter {
|
||||
it.shouldProcess(eventId, event.getClearType(), eventInsert.insertType)
|
||||
}.forEach {
|
||||
it.process(realm, event)
|
||||
}
|
||||
} else {
|
||||
Timber.v("Cannot process event with id $eventId")
|
||||
return@forEach
|
||||
}
|
||||
val domainEvent = event.asDomain()
|
||||
processors.filter {
|
||||
it.shouldProcess(eventId, domainEvent.getClearType(), eventInsert.insertType)
|
||||
}.forEach {
|
||||
it.process(realm, domainEvent)
|
||||
}
|
||||
}
|
||||
|
||||
eventsToIgnore.forEach { getAndTriageEvent(it) }
|
||||
|
||||
realm.where(EventInsertEntity::class.java)
|
||||
.`in`(EventInsertEntityFields.EVENT_ID, idsToDeleteAfterProcess.toTypedArray())
|
||||
.findAll()
|
||||
.deleteAllFromRealm()
|
||||
|
||||
// make the encrypted events not processable: they will be processed again after decryption
|
||||
realm.where(EventInsertEntity::class.java)
|
||||
.`in`(EventInsertEntityFields.EVENT_ID, idsOfEncryptedEvents.toTypedArray())
|
||||
.findAll()
|
||||
.forEach { it.canBeProcessed = false }
|
||||
}
|
||||
processors.forEach { it.onPostProcess() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEvent(realm: Realm, eventId: String): Event? {
|
||||
val event = EventEntity.where(realm, eventId).findFirst()
|
||||
if (event == null) {
|
||||
Timber.v("Event $eventId not found")
|
||||
}
|
||||
return event?.asDomain()
|
||||
}
|
||||
|
||||
private fun canProcessEvent(event: Event): Boolean {
|
||||
// event should be either not encrypted or if encrypted it should contain relatesTo content
|
||||
return event.getClearType() != EventType.ENCRYPTED ||
|
||||
event.content.toModel<EncryptedEventContent>()?.relatesTo != null
|
||||
}
|
||||
|
||||
private fun shouldProcess(eventInsertEntity: EventInsertEntity): Boolean {
|
||||
return processors.any {
|
||||
it.shouldProcess(eventInsertEntity.eventId, eventInsertEntity.eventType, eventInsertEntity.insertType)
|
||||
|
|
|
@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo044
|
|||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo045
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo047
|
||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo048
|
||||
import org.matrix.android.sdk.internal.util.Normalizer
|
||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||
import javax.inject.Inject
|
||||
|
@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
private val normalizer: Normalizer
|
||||
) : MatrixRealmMigration(
|
||||
dbName = "Session",
|
||||
schemaVersion = 47L,
|
||||
schemaVersion = 48L,
|
||||
) {
|
||||
/**
|
||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||
|
@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
|||
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
||||
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
|
||||
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
|
||||
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,8 @@ internal object PollResponseAggregatedSummaryEntityMapper {
|
|||
closedTime = entity.closedTime,
|
||||
localEchos = entity.sourceLocalEchoEvents.toList(),
|
||||
sourceEvents = entity.sourceEvents.toList(),
|
||||
nbOptions = entity.nbOptions
|
||||
nbOptions = entity.nbOptions,
|
||||
encryptedRelatedEventIds = entity.encryptedRelatedEventIds.toList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -40,7 +41,8 @@ internal object PollResponseAggregatedSummaryEntityMapper {
|
|||
nbOptions = model.nbOptions,
|
||||
closedTime = model.closedTime,
|
||||
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
|
||||
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) }
|
||||
sourceLocalEchoEvents = RealmList<String>().apply { addAll(model.localEchos) },
|
||||
encryptedRelatedEventIds = RealmList<String>().apply { addAll(model.encryptedRelatedEventIds) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.database.migration
|
||||
|
||||
import io.realm.DynamicRealm
|
||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||
|
||||
/**
|
||||
* Adding a new field in poll summary to keep track of non decrypted related events.
|
||||
*/
|
||||
internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 48) {
|
||||
|
||||
override fun doMigrate(realm: DynamicRealm) {
|
||||
realm.schema.get("PollResponseAggregatedSummaryEntity")
|
||||
?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java)
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ internal open class EventInsertEntity(
|
|||
var eventType: String = "",
|
||||
/**
|
||||
* This flag will be used to filter EventInsertEntity in EventInsertLiveObserver.
|
||||
* Currently it's set to false when the event content is encrypted.
|
||||
* Currently it's set to false after an event with encrypted content has been processed.
|
||||
*/
|
||||
var canBeProcessed: Boolean = true
|
||||
) : RealmObject() {
|
||||
|
|
|
@ -33,7 +33,9 @@ internal open class PollResponseAggregatedSummaryEntity(
|
|||
|
||||
// The list of the eventIDs used to build the summary (might be out of sync if chunked received from message chunk)
|
||||
var sourceEvents: RealmList<String> = RealmList(),
|
||||
var sourceLocalEchoEvents: RealmList<String> = RealmList()
|
||||
var sourceLocalEchoEvents: RealmList<String> = RealmList(),
|
||||
// list of related event ids which are encrypted due to decryption failure
|
||||
var encryptedRelatedEventIds: RealmList<String> = RealmList(),
|
||||
) : RealmObject() {
|
||||
|
||||
companion object
|
||||
|
|
|
@ -72,7 +72,7 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
|||
SpaceParentSummaryEntity::class,
|
||||
UserPresenceEntity::class,
|
||||
ThreadSummaryEntity::class,
|
||||
ThreadListPageEntity::class
|
||||
ThreadListPageEntity::class,
|
||||
]
|
||||
)
|
||||
internal class SessionRealmModule
|
||||
|
|
|
@ -20,7 +20,6 @@ import io.realm.Realm
|
|||
import io.realm.RealmList
|
||||
import io.realm.RealmQuery
|
||||
import io.realm.kotlin.where
|
||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||
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.EventInsertEntity
|
||||
|
@ -32,10 +31,9 @@ internal fun EventEntity.copyToRealmOrIgnore(realm: Realm, insertType: EventInse
|
|||
.equalTo(EventEntityFields.ROOM_ID, roomId)
|
||||
.findFirst()
|
||||
return if (eventEntity == null) {
|
||||
val canBeProcessed = type != EventType.ENCRYPTED || decryptionResultJson != null
|
||||
val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = canBeProcessed).apply {
|
||||
this.insertType = insertType
|
||||
}
|
||||
val insertEntity = EventInsertEntity(eventId = eventId, eventType = type, canBeProcessed = true)
|
||||
insertEntity.insertType = insertType
|
||||
|
||||
realm.insert(insertEntity)
|
||||
// copy this event entity and return it
|
||||
realm.copyToRealm(this)
|
||||
|
|
|
@ -61,6 +61,7 @@ import org.matrix.android.sdk.internal.di.UserId
|
|||
import org.matrix.android.sdk.internal.session.EventInsertLiveProcessor
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor
|
||||
import org.matrix.android.sdk.internal.session.room.state.StateEventDataSource
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
import timber.log.Timber
|
||||
|
@ -73,6 +74,7 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
private val sessionManager: SessionManager,
|
||||
private val liveLocationAggregationProcessor: LiveLocationAggregationProcessor,
|
||||
private val pollAggregationProcessor: PollAggregationProcessor,
|
||||
private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor,
|
||||
private val editValidator: EventEditValidator,
|
||||
private val clock: Clock,
|
||||
) : EventInsertLiveProcessor {
|
||||
|
@ -140,6 +142,16 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
|
||||
handleReaction(realm, event, roomId, isLocalEcho)
|
||||
}
|
||||
EventType.ENCRYPTED -> {
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||
processEncryptedContent(
|
||||
encryptedEventContent = encryptedEventContent,
|
||||
realm = realm,
|
||||
event = event,
|
||||
roomId = roomId,
|
||||
isLocalEcho = isLocalEcho,
|
||||
)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
if (event.unsignedData?.relations?.annotations != null) {
|
||||
Timber.v("###REACTION Aggregation in room $roomId for event ${event.eventId}")
|
||||
|
@ -170,32 +182,6 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
// As for now Live event processors are not receiving UTD events.
|
||||
// They will get an update if the event is decrypted later
|
||||
EventType.ENCRYPTED -> {
|
||||
// Relation type is in clear, it might be possible to do some things?
|
||||
// Notice that if the event is decrypted later, process be called again
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||
when (encryptedEventContent?.relatesTo?.type) {
|
||||
RelationType.REPLACE -> {
|
||||
Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
|
||||
// A replace!
|
||||
handleReplace(realm, event, roomId, isLocalEcho, encryptedEventContent.relatesTo.eventId)
|
||||
}
|
||||
RelationType.RESPONSE -> {
|
||||
// can we / should we do we something for UTD response??
|
||||
Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
}
|
||||
RelationType.REFERENCE -> {
|
||||
// can we / should we do we something for UTD reference??
|
||||
Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
}
|
||||
RelationType.ANNOTATION -> {
|
||||
// can we / should we do we something for UTD annotation??
|
||||
Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
}
|
||||
}
|
||||
}
|
||||
EventType.REDACTION -> {
|
||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||
?: return
|
||||
|
@ -250,6 +236,36 @@ internal class EventRelationsAggregationProcessor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun processEncryptedContent(
|
||||
encryptedEventContent: EncryptedEventContent?,
|
||||
realm: Realm,
|
||||
event: Event,
|
||||
roomId: String,
|
||||
isLocalEcho: Boolean,
|
||||
) {
|
||||
when (encryptedEventContent?.relatesTo?.type) {
|
||||
RelationType.REPLACE -> {
|
||||
Timber.w("## UTD replace in room $roomId for event ${event.eventId}")
|
||||
}
|
||||
RelationType.RESPONSE -> {
|
||||
Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
}
|
||||
RelationType.REFERENCE -> {
|
||||
Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
encryptedReferenceAggregationProcessor.handle(
|
||||
realm = realm,
|
||||
event = event,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = encryptedEventContent.relatesTo.eventId,
|
||||
)
|
||||
}
|
||||
RelationType.ANNOTATION -> {
|
||||
Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
// OPT OUT serer aggregation until API mature enough
|
||||
private val SHOULD_HANDLE_SERVER_AGREGGATION = false // should be true to work with e2e
|
||||
|
||||
|
|
|
@ -155,6 +155,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
|||
)
|
||||
aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent())
|
||||
|
||||
event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) }
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -180,6 +182,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
|||
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||
}
|
||||
|
||||
event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) }
|
||||
|
||||
if (!isLocalEcho) {
|
||||
ensurePollIsFullyAggregated(roomId, pollEventId)
|
||||
}
|
||||
|
@ -226,4 +230,10 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
|||
fetchPollResponseEventsTask.execute(params)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity: PollResponseAggregatedSummaryEntity, eventId: String) {
|
||||
if (aggregatedPollSummaryEntity.encryptedRelatedEventIds.contains(eventId)) {
|
||||
aggregatedPollSummaryEntity.encryptedRelatedEventIds.remove(eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.matrix.android.sdk.api.session.Session
|
|||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||
|
||||
interface PollAggregationProcessor {
|
||||
internal interface PollAggregationProcessor {
|
||||
/**
|
||||
* Poll start events don't need to be processed by the aggregator.
|
||||
* This function will only handle if the poll is edited and will update the poll summary entity.
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.utd
|
||||
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class EncryptedReferenceAggregationProcessor @Inject constructor() {
|
||||
|
||||
fun handle(
|
||||
realm: Realm,
|
||||
event: Event,
|
||||
isLocalEcho: Boolean,
|
||||
relatedEventId: String?
|
||||
): Boolean {
|
||||
return if (isLocalEcho || relatedEventId.isNullOrEmpty()) {
|
||||
false
|
||||
} else {
|
||||
handlePollReference(realm = realm, event = event, relatedEventId = relatedEventId)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun handlePollReference(
|
||||
realm: Realm,
|
||||
event: Event,
|
||||
relatedEventId: String
|
||||
) {
|
||||
event.eventId?.let { eventId ->
|
||||
val existingRelatedPoll = getPollSummaryWithEventId(realm, relatedEventId)
|
||||
if (eventId !in existingRelatedPoll?.encryptedRelatedEventIds.orEmpty()) {
|
||||
existingRelatedPoll?.encryptedRelatedEventIds?.add(eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPollSummaryWithEventId(realm: Realm, eventId: String): PollResponseAggregatedSummaryEntity? {
|
||||
return realm.where(PollResponseAggregatedSummaryEntity::class.java)
|
||||
.containsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, eventId)
|
||||
.findFirst()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
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.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.EventAnnotationsSummaryEntityFields
|
||||
import org.matrix.android.sdk.test.fakes.FakeClock
|
||||
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||
import org.matrix.android.sdk.test.fakes.FakeStateEventDataSource
|
||||
import org.matrix.android.sdk.test.fakes.givenEqualTo
|
||||
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||
import org.matrix.android.sdk.test.fakes.internal.FakeEventEditValidator
|
||||
import org.matrix.android.sdk.test.fakes.internal.FakeLiveLocationAggregationProcessor
|
||||
import org.matrix.android.sdk.test.fakes.internal.FakePollAggregationProcessor
|
||||
import org.matrix.android.sdk.test.fakes.internal.FakeSessionManager
|
||||
import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor
|
||||
|
||||
private const val A_ROOM_ID = "room-id"
|
||||
private const val AN_EVENT_ID = "event-id"
|
||||
|
||||
internal class EventRelationsAggregationProcessorTest {
|
||||
|
||||
private val fakeStateEventDataSource = FakeStateEventDataSource()
|
||||
private val fakeSessionManager = FakeSessionManager()
|
||||
private val fakeLiveLocationAggregationProcessor = FakeLiveLocationAggregationProcessor()
|
||||
private val fakePollAggregationProcessor = FakePollAggregationProcessor()
|
||||
private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor()
|
||||
private val fakeEventEditValidator = FakeEventEditValidator()
|
||||
private val fakeClock = FakeClock()
|
||||
private val fakeRealm = FakeRealm()
|
||||
|
||||
private val encryptedEventRelationsAggregationProcessor = EventRelationsAggregationProcessor(
|
||||
userId = "userId",
|
||||
stateEventDataSource = fakeStateEventDataSource.instance,
|
||||
sessionId = "sessionId",
|
||||
sessionManager = fakeSessionManager.instance,
|
||||
liveLocationAggregationProcessor = fakeLiveLocationAggregationProcessor.instance,
|
||||
pollAggregationProcessor = fakePollAggregationProcessor.instance,
|
||||
encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance,
|
||||
editValidator = fakeEventEditValidator.instance,
|
||||
clock = fakeClock,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `given an encrypted reference event when process then reference is processed`() {
|
||||
// Given
|
||||
val anEvent = givenAnEvent(
|
||||
eventId = AN_EVENT_ID,
|
||||
roomId = A_ROOM_ID,
|
||||
eventType = EventType.ENCRYPTED,
|
||||
)
|
||||
val relatedEventId = "related-event-id"
|
||||
val encryptedEventContent = givenEncryptedEventContent(
|
||||
relationType = RelationType.REFERENCE,
|
||||
relatedEventId = relatedEventId,
|
||||
)
|
||||
every { anEvent.content } returns encryptedEventContent.toContent()
|
||||
val resultOfReferenceProcess = false
|
||||
fakeEncryptedReferenceAggregationProcessor.givenHandleReturns(resultOfReferenceProcess)
|
||||
givenEventAnnotationsSummary(roomId = A_ROOM_ID, eventId = AN_EVENT_ID, annotationsSummary = null)
|
||||
|
||||
// When
|
||||
encryptedEventRelationsAggregationProcessor.process(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
)
|
||||
|
||||
// Then
|
||||
fakeEncryptedReferenceAggregationProcessor.verifyHandle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = false,
|
||||
relatedEventId = relatedEventId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenAnEvent(
|
||||
eventId: String,
|
||||
roomId: String?,
|
||||
eventType: String,
|
||||
): Event {
|
||||
return mockk<Event>().also {
|
||||
every { it.eventId } returns eventId
|
||||
every { it.roomId } returns roomId
|
||||
every { it.getClearType() } returns eventType
|
||||
}
|
||||
}
|
||||
|
||||
private fun givenEncryptedEventContent(relationType: String, relatedEventId: String): EncryptedEventContent {
|
||||
val relationContent = RelationDefaultContent(
|
||||
eventId = relatedEventId,
|
||||
type = relationType,
|
||||
)
|
||||
return EncryptedEventContent(
|
||||
relatesTo = relationContent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun givenEventAnnotationsSummary(
|
||||
roomId: String,
|
||||
eventId: String,
|
||||
annotationsSummary: EventAnnotationsSummaryEntity?
|
||||
) {
|
||||
fakeRealm.givenWhere<EventAnnotationsSummaryEntity>()
|
||||
.givenEqualTo(EventAnnotationsSummaryEntityFields.ROOM_ID, roomId)
|
||||
.givenEqualTo(EventAnnotationsSummaryEntityFields.EVENT_ID, eventId)
|
||||
.givenFindFirst(annotationsSummary)
|
||||
}
|
||||
}
|
|
@ -25,6 +25,8 @@ import kotlinx.coroutines.test.advanceUntilIdle
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldContain
|
||||
import org.amshove.kluent.shouldNotContain
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.Session
|
||||
|
@ -105,6 +107,24 @@ class DefaultPollAggregationProcessorTest {
|
|||
pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a poll response event with a reference, when processing, then event id is removed from encrypted events list`() {
|
||||
// Given
|
||||
val anotherEventId = "other-event-id"
|
||||
val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity(
|
||||
encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId)
|
||||
)
|
||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity
|
||||
|
||||
// When
|
||||
val result = pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT)
|
||||
|
||||
// Then
|
||||
result.shouldBeTrue()
|
||||
pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID)
|
||||
pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() {
|
||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply {
|
||||
|
@ -132,12 +152,33 @@ class DefaultPollAggregationProcessorTest {
|
|||
// Given
|
||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
||||
every { fakeTaskExecutor.instance.executorScope } returns this
|
||||
|
||||
// When
|
||||
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
|
||||
|
||||
// When
|
||||
val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT)
|
||||
|
||||
// Then
|
||||
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
|
||||
result.shouldBeTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a poll end event, when processing, then event id is removed from encrypted events list`() = runTest {
|
||||
// Given
|
||||
val anotherEventId = "other-event-id"
|
||||
val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity(
|
||||
encryptedRelatedEventIds = RealmList(AN_EVENT_ID, anotherEventId)
|
||||
)
|
||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns pollResponseAggregatedSummaryEntity
|
||||
every { fakeTaskExecutor.instance.executorScope } returns this
|
||||
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
|
||||
|
||||
// When
|
||||
val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT)
|
||||
|
||||
// Then
|
||||
result.shouldBeTrue()
|
||||
pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldNotContain(AN_EVENT_ID)
|
||||
pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anotherEventId)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -145,12 +186,13 @@ class DefaultPollAggregationProcessorTest {
|
|||
// Given
|
||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
||||
every { fakeTaskExecutor.instance.executorScope } returns this
|
||||
|
||||
// When
|
||||
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false)
|
||||
|
||||
// When
|
||||
val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT)
|
||||
|
||||
// Then
|
||||
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
|
||||
result.shouldBeTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.utd
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.realm.RealmList
|
||||
import org.amshove.kluent.shouldBeFalse
|
||||
import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldContain
|
||||
import org.junit.Test
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntity
|
||||
import org.matrix.android.sdk.internal.database.model.PollResponseAggregatedSummaryEntityFields
|
||||
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||
import org.matrix.android.sdk.test.fakes.givenContainsValue
|
||||
import org.matrix.android.sdk.test.fakes.givenFindFirst
|
||||
|
||||
internal class EncryptedReferenceAggregationProcessorTest {
|
||||
|
||||
private val fakeRealm = FakeRealm()
|
||||
|
||||
private val encryptedReferenceAggregationProcessor = EncryptedReferenceAggregationProcessor()
|
||||
|
||||
@Test
|
||||
fun `given local echo when process then result is false`() {
|
||||
// Given
|
||||
val anEvent = mockk<Event>()
|
||||
val isLocalEcho = true
|
||||
val relatedEventId = "event-id"
|
||||
|
||||
// When
|
||||
val result = encryptedReferenceAggregationProcessor.handle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = relatedEventId,
|
||||
)
|
||||
|
||||
// Then
|
||||
result.shouldBeFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given invalid event id when process then result is false`() {
|
||||
// Given
|
||||
val anEvent = mockk<Event>()
|
||||
val isLocalEcho = false
|
||||
|
||||
// When
|
||||
val result1 = encryptedReferenceAggregationProcessor.handle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = null,
|
||||
)
|
||||
val result2 = encryptedReferenceAggregationProcessor.handle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = "",
|
||||
)
|
||||
|
||||
// Then
|
||||
result1.shouldBeFalse()
|
||||
result2.shouldBeFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given related event id of an existing poll when process then result is true and event id is stored in poll summary`() {
|
||||
// Given
|
||||
val anEventId = "event-id"
|
||||
val anEvent = givenAnEvent(anEventId)
|
||||
val isLocalEcho = false
|
||||
val relatedEventId = "related-event-id"
|
||||
val pollResponseAggregatedSummaryEntity = PollResponseAggregatedSummaryEntity(
|
||||
encryptedRelatedEventIds = RealmList(),
|
||||
)
|
||||
fakeRealm.givenWhere<PollResponseAggregatedSummaryEntity>()
|
||||
.givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId)
|
||||
.givenFindFirst(pollResponseAggregatedSummaryEntity)
|
||||
|
||||
// When
|
||||
val result = encryptedReferenceAggregationProcessor.handle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = relatedEventId,
|
||||
)
|
||||
|
||||
// Then
|
||||
result.shouldBeTrue()
|
||||
pollResponseAggregatedSummaryEntity.encryptedRelatedEventIds.shouldContain(anEventId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given related event id but no existing related poll when process then result is true and event id is not stored`() {
|
||||
// Given
|
||||
val anEventId = "event-id"
|
||||
val anEvent = givenAnEvent(anEventId)
|
||||
val isLocalEcho = false
|
||||
val relatedEventId = "related-event-id"
|
||||
fakeRealm.givenWhere<PollResponseAggregatedSummaryEntity>()
|
||||
.givenContainsValue(PollResponseAggregatedSummaryEntityFields.SOURCE_EVENTS.`$`, relatedEventId)
|
||||
.givenFindFirst(null)
|
||||
|
||||
// When
|
||||
val result = encryptedReferenceAggregationProcessor.handle(
|
||||
realm = fakeRealm.instance,
|
||||
event = anEvent,
|
||||
isLocalEcho = isLocalEcho,
|
||||
relatedEventId = relatedEventId,
|
||||
)
|
||||
|
||||
// Then
|
||||
result.shouldBeTrue()
|
||||
}
|
||||
|
||||
private fun givenAnEvent(eventId: String): Event {
|
||||
return mockk<Event>().also {
|
||||
every { it.eventId } returns eventId
|
||||
}
|
||||
}
|
||||
}
|
|
@ -117,6 +117,14 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIn(
|
|||
return this
|
||||
}
|
||||
|
||||
inline fun <reified T : RealmModel> RealmQuery<T>.givenContainsValue(
|
||||
fieldName: String,
|
||||
value: String,
|
||||
): RealmQuery<T> {
|
||||
every { containsValue(fieldName, value) } 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.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.internal
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.room.EventEditValidator
|
||||
|
||||
internal class FakeEventEditValidator {
|
||||
|
||||
val instance: EventEditValidator = mockk()
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.internal
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.LiveLocationAggregationProcessor
|
||||
|
||||
internal class FakeLiveLocationAggregationProcessor {
|
||||
|
||||
val instance: LiveLocationAggregationProcessor = mockk()
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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.internal
|
||||
|
||||
import io.mockk.mockk
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||
|
||||
internal class FakePollAggregationProcessor {
|
||||
|
||||
val instance: PollAggregationProcessor = mockk()
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.internal.session.room.aggregation.utd
|
||||
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import io.realm.Realm
|
||||
import org.matrix.android.sdk.api.session.events.model.Event
|
||||
import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor
|
||||
|
||||
internal class FakeEncryptedReferenceAggregationProcessor {
|
||||
|
||||
val instance: EncryptedReferenceAggregationProcessor = mockk()
|
||||
|
||||
fun givenHandleReturns(result: Boolean) {
|
||||
every { instance.handle(any(), any(), any(), any()) } returns result
|
||||
}
|
||||
|
||||
fun verifyHandle(
|
||||
realm: Realm,
|
||||
event: Event,
|
||||
isLocalEcho: Boolean,
|
||||
relatedEventId: String?,
|
||||
) {
|
||||
verify { instance.handle(realm, event, isLocalEcho, relatedEventId) }
|
||||
}
|
||||
}
|
|
@ -83,9 +83,14 @@ class PollItemViewStateFactory @Inject constructor(
|
|||
totalVotes: Int,
|
||||
winnerVoteCount: Int?,
|
||||
): PollViewState {
|
||||
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
||||
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||
} else {
|
||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes)
|
||||
}
|
||||
return PollViewState(
|
||||
question = question,
|
||||
votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes),
|
||||
votesStatus = totalVotesText,
|
||||
canVote = false,
|
||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||
|
@ -126,9 +131,14 @@ class PollItemViewStateFactory @Inject constructor(
|
|||
pollResponseSummary: PollResponseData?,
|
||||
totalVotes: Int
|
||||
): PollViewState {
|
||||
val totalVotesText = if (pollResponseSummary?.hasEncryptedRelatedEvents.orFalse()) {
|
||||
stringProvider.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||
} else {
|
||||
stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes)
|
||||
}
|
||||
return PollViewState(
|
||||
question = question,
|
||||
votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes),
|
||||
votesStatus = totalVotesText,
|
||||
canVote = true,
|
||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
||||
|
@ -144,7 +154,11 @@ class PollItemViewStateFactory @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun createReadyPollViewState(question: String, pollCreationInfo: PollCreationInfo?, totalVotes: Int): PollViewState {
|
||||
private fun createReadyPollViewState(
|
||||
question: String,
|
||||
pollCreationInfo: PollCreationInfo?,
|
||||
totalVotes: Int
|
||||
): PollViewState {
|
||||
val totalVotesText = if (totalVotes == 0) {
|
||||
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||
} else {
|
||||
|
|
|
@ -44,7 +44,8 @@ class PollResponseDataFactory @Inject constructor(
|
|||
)
|
||||
},
|
||||
winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0,
|
||||
totalVotes = it.aggregatedContent?.totalVotes ?: 0
|
||||
totalVotes = it.aggregatedContent?.totalVotes ?: 0,
|
||||
hasEncryptedRelatedEvents = it.encryptedRelatedEventIds.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,8 @@ data class PollResponseData(
|
|||
val votes: Map<String, PollVoteSummaryData>?,
|
||||
val totalVotes: Int = 0,
|
||||
val winnerVoteCount: Int = 0,
|
||||
val isClosed: Boolean = false
|
||||
val isClosed: Boolean = false,
|
||||
val hasEncryptedRelatedEvents: Boolean = false,
|
||||
) : Parcelable {
|
||||
|
||||
fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId)
|
||||
|
|
|
@ -131,6 +131,24 @@ class PollItemViewStateFactoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a sent poll state with some decryption error when poll is closed then warning message is displayed`() {
|
||||
// Given
|
||||
val stringProvider = FakeStringProvider()
|
||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
||||
val closedPollSummary = A_POLL_RESPONSE_DATA.copy(isClosed = true, hasEncryptedRelatedEvents = true)
|
||||
val closedPollInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = closedPollSummary)
|
||||
|
||||
// When
|
||||
val pollViewState = pollItemViewStateFactory.create(
|
||||
pollContent = A_POLL_CONTENT,
|
||||
informationData = closedPollInformationData,
|
||||
)
|
||||
|
||||
// Then
|
||||
pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
||||
val stringProvider = FakeStringProvider()
|
||||
|
@ -193,6 +211,34 @@ class PollItemViewStateFactoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a sent poll with decryption failure when my vote exists then a warning message is displayed`() {
|
||||
// Given
|
||||
val stringProvider = FakeStringProvider()
|
||||
val pollItemViewStateFactory = PollItemViewStateFactory(stringProvider.instance)
|
||||
val votedPollData = A_POLL_RESPONSE_DATA.copy(
|
||||
totalVotes = 1,
|
||||
myVote = A_POLL_OPTION_IDS[0],
|
||||
votes = mapOf(A_POLL_OPTION_IDS[0] to PollVoteSummaryData(total = 1, percentage = 1.0)),
|
||||
hasEncryptedRelatedEvents = true,
|
||||
)
|
||||
val disclosedPollContent = A_POLL_CONTENT.copy(
|
||||
unstablePollCreationInfo = A_POLL_CONTENT.getBestPollCreationInfo()?.copy(
|
||||
kind = PollType.DISCLOSED_UNSTABLE
|
||||
),
|
||||
)
|
||||
val votedInformationData = A_MESSAGE_INFORMATION_DATA.copy(pollResponseAggregatedSummary = votedPollData)
|
||||
|
||||
// When
|
||||
val pollViewState = pollItemViewStateFactory.create(
|
||||
pollContent = disclosedPollContent,
|
||||
informationData = votedInformationData,
|
||||
)
|
||||
|
||||
// Then
|
||||
pollViewState.votesStatus shouldBeEqualTo stringProvider.instance.getString(R.string.unable_to_decrypt_some_events_in_poll)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
||||
val stringProvider = FakeStringProvider()
|
||||
|
|
Loading…
Add table
Reference in a new issue