Compare commits
16 Commits
develop
...
feature/mn
Author | SHA1 | Date |
---|---|---|
Maxime NATUREL | 10067d5e2e | |
Maxime NATUREL | 3da449a8fe | |
Maxime NATUREL | 535940a09f | |
Maxime NATUREL | 3e1308c894 | |
Maxime NATUREL | 74c4ca6d7f | |
Maxime NATUREL | 195b2e74b1 | |
Maxime NATUREL | 610c4e3339 | |
Maxime NATUREL | 35d5c96c34 | |
Maxime NATUREL | 0184ff8603 | |
Maxime NATUREL | e8a9db1cf0 | |
Maxime NATUREL | 84351f7b4a | |
Maxime NATUREL | a42dcbecd7 | |
Maxime NATUREL | 9ee6888d4b | |
Maxime NATUREL | 14b75f35b1 | |
Maxime NATUREL | f4cbc0ba7f | |
Maxime NATUREL | 436dbdf684 |
|
@ -0,0 +1 @@
|
||||||
|
[Poll] Warning message on decryption failure of some events
|
|
@ -3190,6 +3190,7 @@
|
||||||
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
|
<string name="open_poll_option_description">Voters see results as soon as they have voted</string>
|
||||||
<string name="closed_poll_option_title">Closed poll</string>
|
<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="closed_poll_option_description">Results are only revealed when you end the poll</string>
|
||||||
|
<string name="unable_to_decrypt_some_events_in_poll">Due to decryption errors, some votes may not be counted</string>
|
||||||
|
|
||||||
<!-- Location -->
|
<!-- Location -->
|
||||||
<string name="location_activity_title_static_sharing">Share location</string>
|
<string name="location_activity_title_static_sharing">Share location</string>
|
||||||
|
|
|
@ -23,5 +23,7 @@ data class PollResponseAggregatedSummary(
|
||||||
val nbOptions: Int = 0,
|
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)
|
// 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 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>,
|
||||||
)
|
)
|
||||||
|
|
|
@ -62,7 +62,9 @@ import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultCreateUnableToDecryptEventEntityTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
|
||||||
|
@ -253,4 +255,7 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
|
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindCreateUnableToDecryptEventEntityTask(task: DefaultCreateUnableToDecryptEventEntityTask): CreateUnableToDecryptEventEntityTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
|
||||||
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.CreateUnableToDecryptEventEntityTask
|
||||||
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
|
||||||
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
import org.matrix.android.sdk.internal.extensions.foldToCallback
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
@ -59,7 +60,8 @@ internal class EventDecryptor @Inject constructor(
|
||||||
private val sendToDeviceTask: SendToDeviceTask,
|
private val sendToDeviceTask: SendToDeviceTask,
|
||||||
private val deviceListManager: DeviceListManager,
|
private val deviceListManager: DeviceListManager,
|
||||||
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
private val ensureOlmSessionsForDevicesAction: EnsureOlmSessionsForDevicesAction,
|
||||||
private val cryptoStore: IMXCryptoStore
|
private val cryptoStore: IMXCryptoStore,
|
||||||
|
private val createUnableToDecryptEventEntityTask: CreateUnableToDecryptEventEntityTask,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -136,6 +138,7 @@ internal class EventDecryptor @Inject constructor(
|
||||||
val eventContent = event.content
|
val eventContent = event.content
|
||||||
if (eventContent == null) {
|
if (eventContent == null) {
|
||||||
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
Timber.tag(loggerTag.value).e("decryptEvent : empty event content")
|
||||||
|
createUnableToDecryptEventEntity(event.eventId)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
|
||||||
} else if (event.isRedacted()) {
|
} else if (event.isRedacted()) {
|
||||||
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
|
// we shouldn't attempt to decrypt a redacted event because the content is cleared and decryption will fail because of null algorithm
|
||||||
|
@ -153,6 +156,7 @@ internal class EventDecryptor @Inject constructor(
|
||||||
if (alg == null) {
|
if (alg == null) {
|
||||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||||
Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
|
Timber.tag(loggerTag.value).e("decryptEvent() : $reason")
|
||||||
|
createUnableToDecryptEventEntity(event.eventId)
|
||||||
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
@ -171,12 +175,20 @@ internal class EventDecryptor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
createUnableToDecryptEventEntity(event.eventId)
|
||||||
throw mxCryptoError
|
throw mxCryptoError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun createUnableToDecryptEventEntity(eventId: String?) {
|
||||||
|
eventId?.let {
|
||||||
|
val params = CreateUnableToDecryptEventEntityTask.Params(eventId = it)
|
||||||
|
createUnableToDecryptEventEntityTask.execute(params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) {
|
private suspend fun markOlmSessionForUnwedging(senderId: String, senderKey: String) {
|
||||||
wedgedMutex.withLock {
|
wedgedMutex.withLock {
|
||||||
val info = WedgedDeviceInfo(senderId, senderKey)
|
val info = WedgedDeviceInfo(senderId, senderKey)
|
||||||
|
|
|
@ -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.internal.crypto.tasks
|
||||||
|
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.db.doRealmTransactionAsync
|
||||||
|
import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This task create a dedicated entity for UTD events so that it can be processed later.
|
||||||
|
*/
|
||||||
|
internal interface CreateUnableToDecryptEventEntityTask : Task<CreateUnableToDecryptEventEntityTask.Params, Unit> {
|
||||||
|
data class Params(
|
||||||
|
val eventId: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultCreateUnableToDecryptEventEntityTask @Inject constructor(
|
||||||
|
@SessionDatabase val realmConfiguration: RealmConfiguration,
|
||||||
|
) : CreateUnableToDecryptEventEntityTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: CreateUnableToDecryptEventEntityTask.Params) {
|
||||||
|
val utdEventEntity = UnableToDecryptEventEntity(eventId = params.eventId)
|
||||||
|
doRealmTransactionAsync(realmConfiguration) { realm ->
|
||||||
|
realm.insert(utdEventEntity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.MigrateSessionTo045
|
||||||
import org.matrix.android.sdk.internal.database.migration.MigrateSessionTo046
|
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.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.Normalizer
|
||||||
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
import org.matrix.android.sdk.internal.util.database.MatrixRealmMigration
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -72,7 +73,7 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
private val normalizer: Normalizer
|
private val normalizer: Normalizer
|
||||||
) : MatrixRealmMigration(
|
) : MatrixRealmMigration(
|
||||||
dbName = "Session",
|
dbName = "Session",
|
||||||
schemaVersion = 47L,
|
schemaVersion = 48L,
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Forces all RealmSessionStoreMigration instances to be equal.
|
* Forces all RealmSessionStoreMigration instances to be equal.
|
||||||
|
@ -129,5 +130,6 @@ internal class RealmSessionStoreMigration @Inject constructor(
|
||||||
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
if (oldVersion < 45) MigrateSessionTo045(realm).perform()
|
||||||
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
|
if (oldVersion < 46) MigrateSessionTo046(realm).perform()
|
||||||
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
|
if (oldVersion < 47) MigrateSessionTo047(realm).perform()
|
||||||
|
if (oldVersion < 48) MigrateSessionTo048(realm).perform()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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.database
|
||||||
|
|
||||||
|
import com.zhuinden.monarchy.Monarchy
|
||||||
|
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.internal.database.mapper.asDomain
|
||||||
|
import org.matrix.android.sdk.internal.database.model.EventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity
|
||||||
|
import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
|
import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class UnableToDecryptEventLiveObserver @Inject constructor(
|
||||||
|
@SessionDatabase realmConfiguration: RealmConfiguration,
|
||||||
|
private val processors: Set<@JvmSuppressWildcards UnableToDecryptEventLiveProcessor>
|
||||||
|
) :
|
||||||
|
RealmLiveEntityObserver<UnableToDecryptEventEntity>(realmConfiguration) {
|
||||||
|
|
||||||
|
private val lock = Mutex()
|
||||||
|
|
||||||
|
override val query = Monarchy.Query {
|
||||||
|
it.where(UnableToDecryptEventEntity::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onChange(results: RealmResults<UnableToDecryptEventEntity>) {
|
||||||
|
observerScope.launch {
|
||||||
|
lock.withLock {
|
||||||
|
if (!results.isLoaded || results.isEmpty()) {
|
||||||
|
return@withLock
|
||||||
|
}
|
||||||
|
val copiedEvents = ArrayList<UnableToDecryptEventEntity>(results.size)
|
||||||
|
Timber.v("UnableToDecryptEventEntity updated with ${results.size} results in db")
|
||||||
|
results.forEach {
|
||||||
|
// don't use copy from realm over there
|
||||||
|
val copiedEvent = UnableToDecryptEventEntity(eventId = it.eventId)
|
||||||
|
copiedEvents.add(copiedEvent)
|
||||||
|
}
|
||||||
|
awaitTransaction(realmConfiguration) { realm ->
|
||||||
|
Timber.v("##Transaction: There are ${copiedEvents.size} events to process ")
|
||||||
|
copiedEvents.forEach { utdEvent ->
|
||||||
|
val eventId = utdEvent.eventId
|
||||||
|
val event = EventEntity.where(realm, eventId).findFirst()
|
||||||
|
if (event == null) {
|
||||||
|
Timber.v("Event $eventId not found")
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
val domainEvent = event.asDomain()
|
||||||
|
processors.forEach {
|
||||||
|
it.process(realm, domainEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
realm.where(UnableToDecryptEventEntity::class.java)
|
||||||
|
.`in`(UnableToDecryptEventEntityFields.EVENT_ID, copiedEvents.map { it.eventId }.toTypedArray())
|
||||||
|
.findAll()
|
||||||
|
.deleteAllFromRealm()
|
||||||
|
}
|
||||||
|
processors.forEach { it.onPostProcess() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,8 @@ internal object PollResponseAggregatedSummaryEntityMapper {
|
||||||
closedTime = entity.closedTime,
|
closedTime = entity.closedTime,
|
||||||
localEchos = entity.sourceLocalEchoEvents.toList(),
|
localEchos = entity.sourceLocalEchoEvents.toList(),
|
||||||
sourceEvents = entity.sourceEvents.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,
|
nbOptions = model.nbOptions,
|
||||||
closedTime = model.closedTime,
|
closedTime = model.closedTime,
|
||||||
sourceEvents = RealmList<String>().apply { addAll(model.sourceEvents) },
|
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,38 @@
|
||||||
|
/*
|
||||||
|
* 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.database.model.UnableToDecryptEventEntityFields
|
||||||
|
import org.matrix.android.sdk.internal.util.database.RealmMigrator
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adding a new field in poll summary to keep track of non decrypted related events.
|
||||||
|
* Adding a new entity UnableToDecryptEventEntity.
|
||||||
|
*/
|
||||||
|
internal class MigrateSessionTo048(realm: DynamicRealm) : RealmMigrator(realm, 47) {
|
||||||
|
|
||||||
|
override fun doMigrate(realm: DynamicRealm) {
|
||||||
|
realm.schema.get("PollResponseAggregatedSummaryEntity")
|
||||||
|
?.addRealmListField(PollResponseAggregatedSummaryEntityFields.ENCRYPTED_RELATED_EVENT_IDS.`$`, String::class.java)
|
||||||
|
|
||||||
|
realm.schema.create("UnableToDecryptEventEntity")
|
||||||
|
?.addField(UnableToDecryptEventEntityFields.EVENT_ID, String::class.java)
|
||||||
|
?.setRequired(UnableToDecryptEventEntityFields.EVENT_ID, true)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
// 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 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() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
|
|
@ -72,7 +72,8 @@ import org.matrix.android.sdk.internal.database.model.threads.ThreadSummaryEntit
|
||||||
SpaceParentSummaryEntity::class,
|
SpaceParentSummaryEntity::class,
|
||||||
UserPresenceEntity::class,
|
UserPresenceEntity::class,
|
||||||
ThreadSummaryEntity::class,
|
ThreadSummaryEntity::class,
|
||||||
ThreadListPageEntity::class
|
ThreadListPageEntity::class,
|
||||||
|
UnableToDecryptEventEntity::class,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
internal class SessionRealmModule
|
internal class SessionRealmModule
|
||||||
|
|
|
@ -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.internal.database.model
|
||||||
|
|
||||||
|
import io.realm.RealmObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to get notification on new UTD events. Since these events cannot be processed
|
||||||
|
* in EventInsertEntity, we should introduce a dedicated entity for that.
|
||||||
|
*/
|
||||||
|
internal open class UnableToDecryptEventEntity(
|
||||||
|
var eventId: String = "",
|
||||||
|
) : RealmObject()
|
|
@ -50,6 +50,7 @@ import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||||
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
import org.matrix.android.sdk.internal.database.RealmSessionProvider
|
||||||
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
import org.matrix.android.sdk.internal.database.SessionRealmConfigurationFactory
|
||||||
|
import org.matrix.android.sdk.internal.database.UnableToDecryptEventLiveObserver
|
||||||
import org.matrix.android.sdk.internal.di.Authenticated
|
import org.matrix.android.sdk.internal.di.Authenticated
|
||||||
import org.matrix.android.sdk.internal.di.CacheDirectory
|
import org.matrix.android.sdk.internal.di.CacheDirectory
|
||||||
import org.matrix.android.sdk.internal.di.DeviceId
|
import org.matrix.android.sdk.internal.di.DeviceId
|
||||||
|
@ -84,6 +85,7 @@ import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
||||||
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
|
import org.matrix.android.sdk.internal.session.integrationmanager.IntegrationManager
|
||||||
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
import org.matrix.android.sdk.internal.session.openid.DefaultOpenIdService
|
||||||
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
import org.matrix.android.sdk.internal.session.permalinks.DefaultPermalinkService
|
||||||
|
import org.matrix.android.sdk.internal.session.room.EncryptedEventRelationsAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.EventRelationsAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.DefaultPollAggregationProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
import org.matrix.android.sdk.internal.session.room.aggregation.poll.PollAggregationProcessor
|
||||||
|
@ -346,6 +348,10 @@ internal abstract class SessionModule {
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver
|
abstract fun bindEventInsertObserver(observer: EventInsertLiveObserver): SessionLifecycleObserver
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindUnableToDecryptEventObserver(observer: UnableToDecryptEventLiveObserver): SessionLifecycleObserver
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
@IntoSet
|
@IntoSet
|
||||||
abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver
|
abstract fun bindIntegrationManager(manager: IntegrationManager): SessionLifecycleObserver
|
||||||
|
@ -405,4 +411,8 @@ internal abstract class SessionModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor
|
abstract fun bindPollAggregationProcessor(processor: DefaultPollAggregationProcessor): PollAggregationProcessor
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@IntoSet
|
||||||
|
abstract fun bindEncryptedEventRelationsAggregationProcessor(processor: EncryptedEventRelationsAggregationProcessor): UnableToDecryptEventLiveProcessor
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright 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
|
||||||
|
|
||||||
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
|
||||||
|
internal interface UnableToDecryptEventLiveProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process the given event.
|
||||||
|
* @param realm a realm instance
|
||||||
|
* @param event the event to be processed
|
||||||
|
* @return true if it has been processed, false if it was ignored.
|
||||||
|
*/
|
||||||
|
fun process(realm: Realm, event: Event): Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after transaction.
|
||||||
|
* Maybe you prefer to process the events outside of the realm transaction.
|
||||||
|
*/
|
||||||
|
suspend fun onPostProcess() {
|
||||||
|
// Noop by default
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.realm.Realm
|
||||||
|
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.LocalEcho
|
||||||
|
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.toModel
|
||||||
|
import org.matrix.android.sdk.internal.session.UnableToDecryptEventLiveProcessor
|
||||||
|
import org.matrix.android.sdk.internal.session.room.aggregation.utd.EncryptedReferenceAggregationProcessor
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class EncryptedEventRelationsAggregationProcessor @Inject constructor(
|
||||||
|
private val encryptedReferenceAggregationProcessor: EncryptedReferenceAggregationProcessor,
|
||||||
|
) : UnableToDecryptEventLiveProcessor {
|
||||||
|
|
||||||
|
override fun process(realm: Realm, event: Event): Boolean {
|
||||||
|
val roomId = event.roomId
|
||||||
|
return if (roomId == null) {
|
||||||
|
Timber.w("Event has no room id ${event.eventId}")
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val isLocalEcho = LocalEcho.isLocalEchoId(event.eventId ?: "")
|
||||||
|
|
||||||
|
return when (event.getClearType()) {
|
||||||
|
EventType.ENCRYPTED -> {
|
||||||
|
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||||
|
processEncryptedContent(
|
||||||
|
encryptedEventContent = encryptedEventContent,
|
||||||
|
realm = realm,
|
||||||
|
event = event,
|
||||||
|
roomId = roomId,
|
||||||
|
isLocalEcho = isLocalEcho,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processEncryptedContent(
|
||||||
|
encryptedEventContent: EncryptedEventContent?,
|
||||||
|
realm: Realm,
|
||||||
|
event: Event,
|
||||||
|
roomId: String,
|
||||||
|
isLocalEcho: Boolean,
|
||||||
|
): Boolean {
|
||||||
|
return when (encryptedEventContent?.relatesTo?.type) {
|
||||||
|
RelationType.REPLACE -> {
|
||||||
|
Timber.w("## UTD replace in room $roomId for event ${event.eventId}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
RelationType.RESPONSE -> {
|
||||||
|
// can we / should we do we something for UTD response??
|
||||||
|
Timber.w("## UTD response in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
RelationType.REFERENCE -> {
|
||||||
|
// can we / should we do we something for UTD reference??
|
||||||
|
Timber.w("## UTD reference in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||||
|
val result = encryptedReferenceAggregationProcessor.handle(
|
||||||
|
realm = realm,
|
||||||
|
event = event,
|
||||||
|
isLocalEcho = isLocalEcho,
|
||||||
|
relatedEventId = encryptedEventContent.relatesTo.eventId,
|
||||||
|
)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
RelationType.ANNOTATION -> {
|
||||||
|
// can we / should we do we something for UTD annotation??
|
||||||
|
Timber.w("## UTD annotation in room $roomId related to ${encryptedEventContent.relatesTo.eventId}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ 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.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
import org.matrix.android.sdk.api.session.events.model.getRelationContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
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.events.model.toModel
|
||||||
|
@ -170,32 +169,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 -> {
|
EventType.REDACTION -> {
|
||||||
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
|
||||||
?: return
|
?: return
|
||||||
|
|
|
@ -155,6 +155,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
||||||
)
|
)
|
||||||
aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent())
|
aggregatedPollSummaryEntity.aggregatedContent = ContentMapper.map(newSumModel.toContent())
|
||||||
|
|
||||||
|
event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) }
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,6 +182,8 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
||||||
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
aggregatedPollSummaryEntity.sourceEvents.add(event.eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.eventId?.let { removeEncryptedRelatedEventIdIfNeeded(aggregatedPollSummaryEntity, it) }
|
||||||
|
|
||||||
if (!isLocalEcho) {
|
if (!isLocalEcho) {
|
||||||
ensurePollIsFullyAggregated(roomId, pollEventId)
|
ensurePollIsFullyAggregated(roomId, pollEventId)
|
||||||
}
|
}
|
||||||
|
@ -226,4 +230,10 @@ internal class DefaultPollAggregationProcessor @Inject constructor(
|
||||||
fetchPollResponseEventsTask.execute(params)
|
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.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
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.
|
* 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.
|
* 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,65 @@
|
||||||
|
/*
|
||||||
|
* 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.crypto.tasks
|
||||||
|
|
||||||
|
import io.mockk.unmockkAll
|
||||||
|
import io.mockk.verify
|
||||||
|
import io.realm.RealmModel
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.internal.database.model.UnableToDecryptEventEntity
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.FakeRealmConfiguration
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
internal class DefaultCreateUnableToDecryptEventEntityTaskTest {
|
||||||
|
|
||||||
|
private val fakeRealmConfiguration = FakeRealmConfiguration()
|
||||||
|
|
||||||
|
private val defaultCreateUnableToDecryptEventEntityTask = DefaultCreateUnableToDecryptEventEntityTask(
|
||||||
|
realmConfiguration = fakeRealmConfiguration.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an event id when execute then insert entity into database`() = runTest {
|
||||||
|
// Given
|
||||||
|
val anEventId = "event-id"
|
||||||
|
val params = CreateUnableToDecryptEventEntityTask.Params(
|
||||||
|
eventId = anEventId,
|
||||||
|
)
|
||||||
|
val fakeRealm = FakeRealm()
|
||||||
|
fakeRealm.givenExecuteTransactionAsync()
|
||||||
|
fakeRealmConfiguration.givenGetRealmInstance(fakeRealm.instance)
|
||||||
|
|
||||||
|
// When
|
||||||
|
defaultCreateUnableToDecryptEventEntityTask.execute(params)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
verify {
|
||||||
|
fakeRealm.instance.insert(match<RealmModel> {
|
||||||
|
it is UnableToDecryptEventEntity && it.eventId == anEventId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,209 @@
|
||||||
|
/*
|
||||||
|
* 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.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeFalse
|
||||||
|
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.test.fakes.FakeRealm
|
||||||
|
import org.matrix.android.sdk.test.fakes.internal.session.room.aggregation.utd.FakeEncryptedReferenceAggregationProcessor
|
||||||
|
|
||||||
|
class EncryptedEventRelationsAggregationProcessorTest {
|
||||||
|
|
||||||
|
private val fakeEncryptedReferenceAggregationProcessor = FakeEncryptedReferenceAggregationProcessor()
|
||||||
|
private val fakeRealm = FakeRealm()
|
||||||
|
|
||||||
|
private val encryptedEventRelationsAggregationProcessor = EncryptedEventRelationsAggregationProcessor(
|
||||||
|
encryptedReferenceAggregationProcessor = fakeEncryptedReferenceAggregationProcessor.instance,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given no room Id when process then result is false`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = null,
|
||||||
|
eventType = EventType.ENCRYPTED,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an encrypted reference event when process then reference is processed`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = "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)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBeEqualTo resultOfReferenceProcess
|
||||||
|
fakeEncryptedReferenceAggregationProcessor.verifyHandle(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
isLocalEcho = false,
|
||||||
|
relatedEventId = relatedEventId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an encrypted replace event when process then result is false`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = "room-id",
|
||||||
|
eventType = EventType.ENCRYPTED,
|
||||||
|
)
|
||||||
|
val relatedEventId = "related-event-id"
|
||||||
|
val encryptedEventContent = givenEncryptedEventContent(
|
||||||
|
relationType = RelationType.REPLACE,
|
||||||
|
relatedEventId = relatedEventId,
|
||||||
|
)
|
||||||
|
every { anEvent.content } returns encryptedEventContent.toContent()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an encrypted response event when process then result is false`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = "room-id",
|
||||||
|
eventType = EventType.ENCRYPTED,
|
||||||
|
)
|
||||||
|
val relatedEventId = "related-event-id"
|
||||||
|
val encryptedEventContent = givenEncryptedEventContent(
|
||||||
|
relationType = RelationType.RESPONSE,
|
||||||
|
relatedEventId = relatedEventId,
|
||||||
|
)
|
||||||
|
every { anEvent.content } returns encryptedEventContent.toContent()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given an encrypted annotation event when process then result is false`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = "room-id",
|
||||||
|
eventType = EventType.ENCRYPTED,
|
||||||
|
)
|
||||||
|
val relatedEventId = "related-event-id"
|
||||||
|
val encryptedEventContent = givenEncryptedEventContent(
|
||||||
|
relationType = RelationType.ANNOTATION,
|
||||||
|
relatedEventId = relatedEventId,
|
||||||
|
)
|
||||||
|
every { anEvent.content } returns encryptedEventContent.toContent()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a non encrypted event when process then result is false`() {
|
||||||
|
// Given
|
||||||
|
val anEvent = givenAnEvent(
|
||||||
|
eventId = "event-id",
|
||||||
|
roomId = "room-id",
|
||||||
|
eventType = EventType.MESSAGE,
|
||||||
|
)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = encryptedEventRelationsAggregationProcessor.process(
|
||||||
|
realm = fakeRealm.instance,
|
||||||
|
event = anEvent,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,8 @@ import kotlinx.coroutines.test.advanceUntilIdle
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.amshove.kluent.shouldBeFalse
|
import org.amshove.kluent.shouldBeFalse
|
||||||
import org.amshove.kluent.shouldBeTrue
|
import org.amshove.kluent.shouldBeTrue
|
||||||
|
import org.amshove.kluent.shouldContain
|
||||||
|
import org.amshove.kluent.shouldNotContain
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
@ -105,6 +107,24 @@ class DefaultPollAggregationProcessorTest {
|
||||||
pollAggregationProcessor.handlePollResponseEvent(session, realm.instance, A_POLL_RESPONSE_EVENT).shouldBeTrue()
|
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
|
@Test
|
||||||
fun `given a poll response event after poll is closed, when processing, then is ignored and returns false`() {
|
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 {
|
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity().apply {
|
||||||
|
@ -132,12 +152,33 @@ class DefaultPollAggregationProcessorTest {
|
||||||
// Given
|
// Given
|
||||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
||||||
every { fakeTaskExecutor.instance.executorScope } returns this
|
every { fakeTaskExecutor.instance.executorScope } returns this
|
||||||
|
|
||||||
// When
|
|
||||||
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
|
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, true)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT)
|
||||||
|
|
||||||
// Then
|
// 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
|
@Test
|
||||||
|
@ -145,12 +186,13 @@ class DefaultPollAggregationProcessorTest {
|
||||||
// Given
|
// Given
|
||||||
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
every { realm.instance.createObject(PollResponseAggregatedSummaryEntity::class.java) } returns PollResponseAggregatedSummaryEntity()
|
||||||
every { fakeTaskExecutor.instance.executorScope } returns this
|
every { fakeTaskExecutor.instance.executorScope } returns this
|
||||||
|
|
||||||
// When
|
|
||||||
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false)
|
val powerLevelsHelper = mockRedactionPowerLevels(A_USER_ID_1, false)
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT)
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
pollAggregationProcessor.handlePollEndEvent(session, powerLevelsHelper, realm.instance, A_POLL_END_EVENT).shouldBeTrue()
|
result.shouldBeTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import io.mockk.mockk
|
||||||
import io.mockk.runs
|
import io.mockk.runs
|
||||||
import io.mockk.verify
|
import io.mockk.verify
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import io.realm.Realm.Transaction
|
||||||
import io.realm.RealmModel
|
import io.realm.RealmModel
|
||||||
import io.realm.RealmObject
|
import io.realm.RealmObject
|
||||||
import io.realm.RealmQuery
|
import io.realm.RealmQuery
|
||||||
|
@ -42,6 +43,13 @@ internal class FakeRealm {
|
||||||
inline fun <reified T : RealmModel> verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) {
|
inline fun <reified T : RealmModel> verifyInsertOrUpdate(crossinline verification: MockKVerificationScope.() -> T) {
|
||||||
verify { instance.insertOrUpdate(verification()) }
|
verify { instance.insertOrUpdate(verification()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenExecuteTransactionAsync() {
|
||||||
|
every { instance.executeTransactionAsync(any()) } answers {
|
||||||
|
firstArg<Transaction>().execute(instance)
|
||||||
|
mockk()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : RealmModel> RealmQuery<T>.givenFindFirst(
|
inline fun <reified T : RealmModel> RealmQuery<T>.givenFindFirst(
|
||||||
|
@ -117,6 +125,14 @@ inline fun <reified T : RealmModel> RealmQuery<T>.givenIn(
|
||||||
return this
|
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.
|
* Should be called on a mocked RealmObject and not on a real RealmObject so that the underlying final method is mocked.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package org.matrix.android.sdk.test.fakes
|
package org.matrix.android.sdk.test.fakes
|
||||||
|
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import io.mockk.mockkStatic
|
import io.mockk.mockkStatic
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
@ -36,4 +37,9 @@ internal class FakeRealmConfiguration {
|
||||||
secondArg<(Realm) -> T>().invoke(realm)
|
secondArg<(Realm) -> T>().invoke(realm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun givenGetRealmInstance(realm: Realm) {
|
||||||
|
mockkStatic(Realm::class)
|
||||||
|
every { Realm.getInstance(instance) } returns realm
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
totalVotes: Int,
|
||||||
winnerVoteCount: Int?,
|
winnerVoteCount: Int?,
|
||||||
): PollViewState {
|
): PollViewState {
|
||||||
|
val totalVotesText = if (pollResponseSummary?.hasDecryptionError.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(
|
return PollViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_after_ended, totalVotes, totalVotes),
|
votesStatus = totalVotesText,
|
||||||
canVote = false,
|
canVote = false,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
val voteSummary = pollResponseSummary?.getVoteSummaryOfAnOption(answer.id ?: "")
|
||||||
|
@ -126,9 +131,14 @@ class PollItemViewStateFactory @Inject constructor(
|
||||||
pollResponseSummary: PollResponseData?,
|
pollResponseSummary: PollResponseData?,
|
||||||
totalVotes: Int
|
totalVotes: Int
|
||||||
): PollViewState {
|
): PollViewState {
|
||||||
|
val totalVotesText = if (pollResponseSummary?.hasDecryptionError.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(
|
return PollViewState(
|
||||||
question = question,
|
question = question,
|
||||||
votesStatus = stringProvider.getQuantityString(R.plurals.poll_total_vote_count_before_ended_and_voted, totalVotes, totalVotes),
|
votesStatus = totalVotesText,
|
||||||
canVote = true,
|
canVote = true,
|
||||||
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
optionViewStates = pollCreationInfo?.answers?.map { answer ->
|
||||||
val isMyVote = pollResponseSummary?.myVote == answer.id
|
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) {
|
val totalVotesText = if (totalVotes == 0) {
|
||||||
stringProvider.getString(R.string.poll_no_votes_cast)
|
stringProvider.getString(R.string.poll_no_votes_cast)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -110,7 +110,8 @@ class MessageInformationDataFactory @Inject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0,
|
winnerVoteCount = it.aggregatedContent?.winnerVoteCount ?: 0,
|
||||||
totalVotes = it.aggregatedContent?.totalVotes ?: 0
|
totalVotes = it.aggregatedContent?.totalVotes ?: 0,
|
||||||
|
hasDecryptionError = it.encryptedRelatedEventIds.isNotEmpty(),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
hasBeenEdited = event.hasBeenEdited(),
|
hasBeenEdited = event.hasBeenEdited(),
|
||||||
|
|
|
@ -90,7 +90,8 @@ data class PollResponseData(
|
||||||
val votes: Map<String, PollVoteSummaryData>?,
|
val votes: Map<String, PollVoteSummaryData>?,
|
||||||
val totalVotes: Int = 0,
|
val totalVotes: Int = 0,
|
||||||
val winnerVoteCount: Int = 0,
|
val winnerVoteCount: Int = 0,
|
||||||
val isClosed: Boolean = false
|
val isClosed: Boolean = false,
|
||||||
|
val hasDecryptionError: Boolean = false,
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
|
|
||||||
fun getVoteSummaryOfAnOption(optionId: String) = votes?.get(optionId)
|
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, hasDecryptionError = 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
|
@Test
|
||||||
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
fun `given a sent poll when undisclosed poll type is selected then poll is votable and option states are PollUndisclosed`() {
|
||||||
val stringProvider = FakeStringProvider()
|
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)),
|
||||||
|
hasDecryptionError = 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
|
@Test
|
||||||
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
fun `given a sent poll when poll type is disclosed then poll is votable and option view states are PollReady`() {
|
||||||
val stringProvider = FakeStringProvider()
|
val stringProvider = FakeStringProvider()
|
||||||
|
|
Loading…
Reference in New Issue