Merge pull request #3501 from element-hq/bma/testRustMatrixClient

Test RustMatrixClient and other classes in the matrix module
This commit is contained in:
Benoit Marty 2024-09-20 10:39:49 +02:00 committed by GitHub
commit 39f0703765
70 changed files with 1597 additions and 137 deletions

View file

@ -94,7 +94,7 @@ sealed interface NotificationContent {
data object RoomHistoryVisibility : StateEvent data object RoomHistoryVisibility : StateEvent
data object RoomJoinRules : StateEvent data object RoomJoinRules : StateEvent
data class RoomMemberContent( data class RoomMemberContent(
val userId: String, val userId: UserId,
val membershipState: RoomMembershipState val membershipState: RoomMembershipState
) : StateEvent ) : StateEvent
@ -108,6 +108,10 @@ sealed interface NotificationContent {
data object SpaceChild : StateEvent data object SpaceChild : StateEvent
data object SpaceParent : StateEvent data object SpaceParent : StateEvent
} }
data class Invite(
val senderId: UserId,
) : NotificationContent
} }
enum class CallNotifyType { enum class CallNotifyType {

View file

@ -9,8 +9,7 @@ package io.element.android.libraries.matrix.api.notification
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
interface NotificationService { interface NotificationService {
suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId): Result<NotificationData?> suspend fun getNotification(roomId: RoomId, eventId: EventId): Result<NotificationData?>
} }

View file

@ -46,7 +46,9 @@ dependencies {
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)
testImplementation(libs.test.robolectric) testImplementation(libs.test.robolectric)
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.sessionStorage.implMemory) testImplementation(projects.libraries.sessionStorage.implMemory)
testImplementation(projects.libraries.sessionStorage.test) testImplementation(projects.libraries.sessionStorage.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import org.matrix.rustcomponents.sdk.ClientBuilder
import javax.inject.Inject
interface ClientBuilderProvider {
fun provide(): ClientBuilder
}
@ContributesBinding(AppScope::class)
class RustClientBuilderProvider @Inject constructor() : ClientBuilderProvider {
override fun provide(): ClientBuilder {
return ClientBuilder()
}
}

View file

@ -54,6 +54,7 @@ import io.element.android.libraries.matrix.impl.pushers.RustPushersService
import io.element.android.libraries.matrix.impl.room.RoomContentForwarder import io.element.android.libraries.matrix.impl.room.RoomContentForwarder
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
import io.element.android.libraries.matrix.impl.room.RustRoomFactory import io.element.android.libraries.matrix.impl.room.RustRoomFactory
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewMapper import io.element.android.libraries.matrix.impl.room.preview.RoomPreviewMapper
import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService import io.element.android.libraries.matrix.impl.roomdirectory.RustRoomDirectoryService
import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory import io.element.android.libraries.matrix.impl.roomlist.RoomListFactory
@ -115,14 +116,15 @@ import org.matrix.rustcomponents.sdk.SyncService as ClientSyncService
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class RustMatrixClient( class RustMatrixClient(
private val client: Client, private val client: Client,
private val syncService: ClientSyncService, private val baseDirectory: File,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val appCoroutineScope: CoroutineScope, private val appCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers,
private val baseDirectory: File,
baseCacheDirectory: File,
private val clock: SystemClock,
private val sessionDelegate: RustClientSessionDelegate, private val sessionDelegate: RustClientSessionDelegate,
syncService: ClientSyncService,
dispatchers: CoroutineDispatchers,
baseCacheDirectory: File,
clock: SystemClock,
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
) : MatrixClient { ) : MatrixClient {
override val sessionId: UserId = UserId(client.userId()) override val sessionId: UserId = UserId(client.userId())
override val deviceId: DeviceId = DeviceId(client.deviceId()) override val deviceId: DeviceId = DeviceId(client.deviceId())
@ -138,7 +140,7 @@ class RustMatrixClient(
) )
private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService) private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(syncService)
private val notificationClient = runBlocking { client.notificationClient(notificationProcessSetup) } private val notificationClient = runBlocking { client.notificationClient(notificationProcessSetup) }
private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock) private val notificationService = RustNotificationService(notificationClient, dispatchers, clock)
private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers) private val notificationSettingsService = RustNotificationSettingsService(client, dispatchers)
.apply { start() } .apply { start() }
private val encryptionService = RustEncryptionService( private val encryptionService = RustEncryptionService(
@ -185,6 +187,7 @@ class RustMatrixClient(
systemClock = clock, systemClock = clock,
roomContentForwarder = RoomContentForwarder(innerRoomListService), roomContentForwarder = RoomContentForwarder(innerRoomListService),
roomSyncSubscriber = roomSyncSubscriber, roomSyncSubscriber = roomSyncSubscriber,
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
) )
override val mediaLoader: MatrixMediaLoader = RustMediaLoader( override val mediaLoader: MatrixMediaLoader = RustMediaLoader(

View file

@ -16,6 +16,7 @@ import io.element.android.libraries.matrix.impl.certificates.UserCertificatesPro
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.impl.paths.getSessionPaths import io.element.android.libraries.matrix.impl.paths.getSessionPaths
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
import io.element.android.libraries.matrix.impl.room.TimelineEventTypeFilterFactory
import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.matrix.impl.util.anonymizedTokens
import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.network.useragent.UserAgentProvider
import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionData
@ -45,6 +46,8 @@ class RustMatrixClientFactory @Inject constructor(
private val clock: SystemClock, private val clock: SystemClock,
private val utdTracker: UtdTracker, private val utdTracker: UtdTracker,
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
private val clientBuilderProvider: ClientBuilderProvider,
) { ) {
suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) { suspend fun create(sessionData: SessionData): RustMatrixClient = withContext(coroutineDispatchers.io) {
val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers) val sessionDelegate = RustClientSessionDelegate(sessionStore, appCoroutineScope, coroutineDispatchers)
@ -68,14 +71,15 @@ class RustMatrixClientFactory @Inject constructor(
RustMatrixClient( RustMatrixClient(
client = client, client = client,
syncService = syncService, baseDirectory = baseDirectory,
sessionStore = sessionStore, sessionStore = sessionStore,
appCoroutineScope = appCoroutineScope, appCoroutineScope = appCoroutineScope,
sessionDelegate = sessionDelegate,
syncService = syncService,
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
baseDirectory = baseDirectory,
baseCacheDirectory = cacheDirectory, baseCacheDirectory = cacheDirectory,
clock = clock, clock = clock,
sessionDelegate = sessionDelegate, timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
).also { ).also {
Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'")
} }
@ -86,7 +90,7 @@ class RustMatrixClientFactory @Inject constructor(
passphrase: String?, passphrase: String?,
slidingSyncType: ClientBuilderSlidingSync, slidingSyncType: ClientBuilderSlidingSync,
): ClientBuilder { ): ClientBuilder {
return ClientBuilder() return clientBuilderProvider.provide()
.sessionPaths( .sessionPaths(
dataPath = sessionPaths.fileDirectory.absolutePath, dataPath = sessionPaths.fileDirectory.absolutePath,
cachePath = sessionPaths.cacheDirectory.absolutePath, cachePath = sessionPaths.cacheDirectory.absolutePath,

View file

@ -10,10 +10,9 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.isDm
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import org.matrix.rustcomponents.sdk.NotificationEvent import org.matrix.rustcomponents.sdk.NotificationEvent
@ -21,10 +20,9 @@ import org.matrix.rustcomponents.sdk.NotificationItem
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
class NotificationMapper( class NotificationMapper(
sessionId: SessionId,
private val clock: SystemClock, private val clock: SystemClock,
) { ) {
private val notificationContentMapper = NotificationContentMapper(sessionId) private val notificationContentMapper = NotificationContentMapper()
fun map( fun map(
eventId: EventId, eventId: EventId,
@ -56,15 +54,14 @@ class NotificationMapper(
} }
} }
class NotificationContentMapper(private val sessionId: SessionId) { class NotificationContentMapper {
private val timelineEventToNotificationContentMapper = TimelineEventToNotificationContentMapper() private val timelineEventToNotificationContentMapper = TimelineEventToNotificationContentMapper()
fun map(notificationEvent: NotificationEvent): NotificationContent = fun map(notificationEvent: NotificationEvent): NotificationContent =
when (notificationEvent) { when (notificationEvent) {
is NotificationEvent.Timeline -> timelineEventToNotificationContentMapper.map(notificationEvent.event) is NotificationEvent.Timeline -> timelineEventToNotificationContentMapper.map(notificationEvent.event)
is NotificationEvent.Invite -> NotificationContent.StateEvent.RoomMemberContent( is NotificationEvent.Invite -> NotificationContent.Invite(
userId = sessionId.value, senderId = UserId(notificationEvent.sender),
membershipState = RoomMembershipState.INVITE,
) )
} }
} }

View file

@ -10,7 +10,6 @@ package io.element.android.libraries.matrix.impl.notification
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
@ -19,15 +18,13 @@ import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
class RustNotificationService( class RustNotificationService(
sessionId: SessionId,
private val notificationClient: NotificationClient, private val notificationClient: NotificationClient,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
clock: SystemClock, clock: SystemClock,
) : NotificationService { ) : NotificationService {
private val notificationMapper: NotificationMapper = NotificationMapper(sessionId, clock) private val notificationMapper: NotificationMapper = NotificationMapper(clock)
override suspend fun getNotification( override suspend fun getNotification(
userId: SessionId,
roomId: RoomId, roomId: RoomId,
eventId: EventId, eventId: EventId,
): Result<NotificationData?> = withContext(dispatchers.io) { ): Result<NotificationData?> = withContext(dispatchers.io) {

View file

@ -19,9 +19,8 @@ import org.matrix.rustcomponents.sdk.StateEventContent
import org.matrix.rustcomponents.sdk.TimelineEvent import org.matrix.rustcomponents.sdk.TimelineEvent
import org.matrix.rustcomponents.sdk.TimelineEventType import org.matrix.rustcomponents.sdk.TimelineEventType
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
import javax.inject.Inject
class TimelineEventToNotificationContentMapper @Inject constructor() { class TimelineEventToNotificationContentMapper {
fun map(timelineEvent: TimelineEvent): NotificationContent { fun map(timelineEvent: TimelineEvent): NotificationContent {
return timelineEvent.use { return timelineEvent.use {
timelineEvent.eventType().use { eventType -> timelineEvent.eventType().use { eventType ->
@ -52,7 +51,10 @@ private fun StateEventContent.toContent(): NotificationContent.StateEvent {
StateEventContent.RoomHistoryVisibility -> NotificationContent.StateEvent.RoomHistoryVisibility StateEventContent.RoomHistoryVisibility -> NotificationContent.StateEvent.RoomHistoryVisibility
StateEventContent.RoomJoinRules -> NotificationContent.StateEvent.RoomJoinRules StateEventContent.RoomJoinRules -> NotificationContent.StateEvent.RoomJoinRules
is StateEventContent.RoomMemberContent -> { is StateEventContent.RoomMemberContent -> {
NotificationContent.StateEvent.RoomMemberContent(userId, RoomMemberMapper.mapMembership(membershipState)) NotificationContent.StateEvent.RoomMemberContent(
userId = UserId(userId),
membershipState = RoomMemberMapper.mapMembership(membershipState),
)
} }
StateEventContent.RoomName -> NotificationContent.StateEvent.RoomName StateEventContent.RoomName -> NotificationContent.StateEvent.RoomName
StateEventContent.RoomPinnedEvents -> NotificationContent.StateEvent.RoomPinnedEvents StateEventContent.RoomPinnedEvents -> NotificationContent.StateEvent.RoomPinnedEvents

View file

@ -15,14 +15,14 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.RequiredState import org.matrix.rustcomponents.sdk.RequiredState
import org.matrix.rustcomponents.sdk.RoomListServiceInterface import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.RoomSubscription import org.matrix.rustcomponents.sdk.RoomSubscription
import timber.log.Timber import timber.log.Timber
private const val DEFAULT_TIMELINE_LIMIT = 20u private const val DEFAULT_TIMELINE_LIMIT = 20u
class RoomSyncSubscriber( class RoomSyncSubscriber(
private val roomListService: RoomListServiceInterface, private val roomListService: RoomListService,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
) { ) {
private val subscribedRoomIds = mutableSetOf<RoomId>() private val subscribedRoomIds = mutableSetOf<RoomId>()

View file

@ -27,12 +27,10 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.FilterTimelineEventType
import org.matrix.rustcomponents.sdk.Membership import org.matrix.rustcomponents.sdk.Membership
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.RoomListException import org.matrix.rustcomponents.sdk.RoomListException
import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
import timber.log.Timber import timber.log.Timber
import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService import org.matrix.rustcomponents.sdk.RoomListService as InnerRoomListService
@ -49,6 +47,7 @@ class RustRoomFactory(
private val roomListService: RoomListService, private val roomListService: RoomListService,
private val innerRoomListService: InnerRoomListService, private val innerRoomListService: InnerRoomListService,
private val roomSyncSubscriber: RoomSyncSubscriber, private val roomSyncSubscriber: RoomSyncSubscriber,
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
) { ) {
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val dispatcher = dispatchers.io.limitedParallelism(1) private val dispatcher = dispatchers.io.limitedParallelism(1)
@ -74,11 +73,7 @@ class RustRoomFactory(
private val eventFilters = TimelineConfig.excludedEvents private val eventFilters = TimelineConfig.excludedEvents
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
?.let { listStateEventType -> ?.let { listStateEventType ->
TimelineEventTypeFilter.exclude( timelineEventTypeFilterFactory.create(listStateEventType)
listStateEventType.map { stateEventType ->
FilterTimelineEventType.State(stateEventType.map())
}
)
} }
suspend fun destroy() { suspend fun destroy() {

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.room
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.room.StateEventType
import org.matrix.rustcomponents.sdk.FilterTimelineEventType
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
import javax.inject.Inject
interface TimelineEventTypeFilterFactory {
fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter
}
@ContributesBinding(AppScope::class)
class RustTimelineEventTypeFilterFactory @Inject constructor() : TimelineEventTypeFilterFactory {
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
return TimelineEventTypeFilter.exclude(
listStateEventType.map { stateEventType ->
FilterTimelineEventType.State(stateEventType.map())
}
)
}
}

View file

@ -11,10 +11,12 @@ import io.element.android.libraries.matrix.api.room.message.RoomMessage
import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper
import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem import org.matrix.rustcomponents.sdk.EventTimelineItem as RustEventTimelineItem
class RoomMessageFactory { class RoomMessageFactory(
private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(),
) {
fun create(eventTimelineItem: RustEventTimelineItem?): RoomMessage? { fun create(eventTimelineItem: RustEventTimelineItem?): RoomMessage? {
eventTimelineItem ?: return null eventTimelineItem ?: return null
val mappedTimelineItem = EventTimelineItemMapper().map(eventTimelineItem) val mappedTimelineItem = eventTimelineItemMapper.map(eventTimelineItem)
return RoomMessage( return RoomMessage(
eventId = mappedTimelineItem.eventId ?: return null, eventId = mappedTimelineItem.eventId ?: return null,
event = mappedTimelineItem, event = mappedTimelineItem,

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.roomdirectory package io.element.android.libraries.matrix.impl.roomdirectory
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@ -17,10 +18,12 @@ import timber.log.Timber
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class RoomDirectorySearchProcessor( class RoomDirectorySearchProcessor(
private val roomDescriptions: MutableSharedFlow<List<RoomDescription>>,
private val coroutineContext: CoroutineContext, private val coroutineContext: CoroutineContext,
private val roomDescriptionMapper: RoomDescriptionMapper,
) { ) {
private val roomDescriptions: MutableSharedFlow<List<RoomDescription>> = MutableSharedFlow(replay = 1)
val roomDescriptionsFlow: Flow<List<RoomDescription>> = roomDescriptions
private val roomDescriptionMapper: RoomDescriptionMapper = RoomDescriptionMapper()
private val mutex = Mutex() private val mutex = Mutex()
suspend fun postUpdates(updates: List<RoomDirectorySearchEntryUpdate>) { suspend fun postUpdates(updates: List<RoomDirectorySearchEntryUpdate>) {

View file

@ -7,12 +7,10 @@
package io.element.android.libraries.matrix.impl.roomdirectory package io.element.android.libraries.matrix.impl.roomdirectory
import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
@ -27,8 +25,7 @@ class RustRoomDirectoryList(
private val coroutineContext: CoroutineContext, private val coroutineContext: CoroutineContext,
) : RoomDirectoryList { ) : RoomDirectoryList {
private val hasMoreToLoad = MutableStateFlow(true) private val hasMoreToLoad = MutableStateFlow(true)
private val items = MutableSharedFlow<List<RoomDescription>>(replay = 1) private val processor = RoomDirectorySearchProcessor(coroutineContext)
private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper())
init { init {
launchIn(coroutineScope) launchIn(coroutineScope)
@ -77,7 +74,7 @@ class RustRoomDirectoryList(
} }
override val state: Flow<RoomDirectoryList.State> = override val state: Flow<RoomDirectoryList.State> =
combine(hasMoreToLoad, items) { hasMoreToLoad, items -> combine(hasMoreToLoad, processor.roomDescriptionsFlow) { hasMoreToLoad, items ->
RoomDirectoryList.State( RoomDirectoryList.State(
hasMoreToLoad = hasMoreToLoad, hasMoreToLoad = hasMoreToLoad,
items = items items = items

View file

@ -25,21 +25,21 @@ import org.matrix.rustcomponents.sdk.RoomListEntriesDynamicFilterKind
import org.matrix.rustcomponents.sdk.RoomListLoadingState import org.matrix.rustcomponents.sdk.RoomListLoadingState
import org.matrix.rustcomponents.sdk.RoomListService import org.matrix.rustcomponents.sdk.RoomListService
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import org.matrix.rustcomponents.sdk.RoomList as InnerRoomList import org.matrix.rustcomponents.sdk.RoomList as InnerRoomList
internal class RoomListFactory( internal class RoomListFactory(
private val innerRoomListService: RoomListService, private val innerRoomListService: RoomListService,
private val sessionCoroutineScope: CoroutineScope, private val sessionCoroutineScope: CoroutineScope,
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) { ) {
private val roomSummaryDetailsFactory: RoomSummaryDetailsFactory = RoomSummaryDetailsFactory()
/** /**
* Creates a room list that can be used to load more rooms and filter them dynamically. * Creates a room list that can be used to load more rooms and filter them dynamically.
*/ */
fun createRoomList( fun createRoomList(
pageSize: Int, pageSize: Int,
coroutineContext: CoroutineContext,
coroutineScope: CoroutineScope = sessionCoroutineScope, coroutineScope: CoroutineScope = sessionCoroutineScope,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
initialFilter: RoomListFilter = RoomListFilter.all(), initialFilter: RoomListFilter = RoomListFilter.all(),
innerProvider: suspend () -> InnerRoomList innerProvider: suspend () -> InnerRoomList
): DynamicRoomList { ): DynamicRoomList {

View file

@ -19,7 +19,9 @@ import io.element.android.libraries.matrix.impl.room.message.RoomMessageFactory
import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
class RoomSummaryDetailsFactory(private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory()) { class RoomSummaryDetailsFactory(
private val roomMessageFactory: RoomMessageFactory = RoomMessageFactory(),
) {
suspend fun create(roomListItem: RoomListItem): RoomSummary { suspend fun create(roomListItem: RoomListItem): RoomSummary {
val roomInfo = roomListItem.roomInfo() val roomInfo = roomListItem.roomInfo()
val latestRoomMessage = roomListItem.latestEvent().use { event -> val latestRoomMessage = roomListItem.latestEvent().use { event ->

View file

@ -31,10 +31,10 @@ private const val DEFAULT_PAGE_SIZE = 20
internal class RustRoomListService( internal class RustRoomListService(
private val innerRoomListService: InnerRustRoomListService, private val innerRoomListService: InnerRustRoomListService,
private val sessionCoroutineScope: CoroutineScope,
private val sessionDispatcher: CoroutineDispatcher, private val sessionDispatcher: CoroutineDispatcher,
private val roomListFactory: RoomListFactory, private val roomListFactory: RoomListFactory,
private val roomSyncSubscriber: RoomSyncSubscriber, private val roomSyncSubscriber: RoomSyncSubscriber,
sessionCoroutineScope: CoroutineScope,
) : RoomListService { ) : RoomListService {
override fun createRoomList( override fun createRoomList(
pageSize: Int, pageSize: Int,

View file

@ -19,8 +19,8 @@ import org.matrix.rustcomponents.sdk.TimelineItem
class MatrixTimelineItemMapper( class MatrixTimelineItemMapper(
private val fetchDetailsForEvent: suspend (EventId) -> Result<Unit>, private val fetchDetailsForEvent: suspend (EventId) -> Result<Unit>,
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val virtualTimelineItemMapper: VirtualTimelineItemMapper = VirtualTimelineItemMapper(), private val virtualTimelineItemMapper: VirtualTimelineItemMapper,
private val eventTimelineItemMapper: EventTimelineItemMapper = EventTimelineItemMapper(), private val eventTimelineItemMapper: EventTimelineItemMapper,
) { ) {
fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use { fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use {
val uniqueId = UniqueId(timelineItem.uniqueId()) val uniqueId = UniqueId(timelineItem.uniqueId())

View file

@ -34,7 +34,9 @@ import org.matrix.rustcomponents.sdk.ProfileDetails as RustProfileDetails
import org.matrix.rustcomponents.sdk.Receipt as RustReceipt import org.matrix.rustcomponents.sdk.Receipt as RustReceipt
import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin import uniffi.matrix_sdk_ui.EventItemOrigin as RustEventItemOrigin
class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper()) { class EventTimelineItemMapper(
private val contentMapper: TimelineEventContentMapper = TimelineEventContentMapper(),
) {
fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use {
EventTimelineItem( EventTimelineItem(
eventId = it.eventId()?.let(::EventId), eventId = it.eventId()?.let(::EventId),

View file

@ -37,7 +37,9 @@ import org.matrix.rustcomponents.sdk.MembershipChange as RustMembershipChange
import org.matrix.rustcomponents.sdk.OtherState as RustOtherState import org.matrix.rustcomponents.sdk.OtherState as RustOtherState
import uniffi.matrix_sdk_crypto.UtdCause as RustUtdCause import uniffi.matrix_sdk_crypto.UtdCause as RustUtdCause
class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { class TimelineEventContentMapper(
private val eventMessageMapper: EventMessageMapper = EventMessageMapper(),
) {
fun map(content: TimelineItemContent): EventContent { fun map(content: TimelineItemContent): EventContent {
return content.use { return content.use {
content.kind().use { kind -> content.kind().use { kind ->

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClientBuilder
import org.matrix.rustcomponents.sdk.ClientBuilder
class FakeClientBuilderProvider : ClientBuilderProvider {
override fun provide(): ClientBuilder {
return FakeRustClientBuilder()
}
}

View file

@ -0,0 +1,53 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class RustClientSessionDelegateTest {
@Test
fun `saveSessionInKeychain should update the store`() = runTest {
val sessionStore = InMemorySessionStore()
sessionStore.storeData(
aSessionData(
accessToken = "anAccessToken",
refreshToken = "aRefreshToken",
)
)
val sut = aRustClientSessionDelegate(sessionStore)
sut.saveSessionInKeychain(
aRustSession(
accessToken = "at",
refreshToken = "rt",
)
)
runCurrent()
val result = sessionStore.getLatestSession()
assertThat(result!!.accessToken).isEqualTo("at")
assertThat(result.refreshToken).isEqualTo("rt")
}
}
fun TestScope.aRustClientSessionDelegate(
sessionStore: SessionStore = InMemorySessionStore(),
) = RustClientSessionDelegate(
sessionStore = sessionStore,
appCoroutineScope = this,
coroutineDispatchers = testCoroutineDispatchers(),
)

View file

@ -0,0 +1,57 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.analytics.UtdTracker
import io.element.android.libraries.matrix.impl.auth.FakeProxyProvider
import io.element.android.libraries.matrix.impl.auth.FakeUserCertificatesProvider
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.io.File
class RustMatrixClientFactoryTest {
@Test
fun test() = runTest {
val sut = createRustMatrixClientFactory()
val result = sut.create(aSessionData())
assertThat(result.sessionId).isEqualTo(SessionId("@alice:server.org"))
result.close()
}
}
fun TestScope.createRustMatrixClientFactory(
baseDirectory: File = File("/base"),
cacheDirectory: File = File("/cache"),
sessionStore: SessionStore = InMemorySessionStore(),
) = RustMatrixClientFactory(
baseDirectory = baseDirectory,
cacheDirectory = cacheDirectory,
appCoroutineScope = this,
coroutineDispatchers = testCoroutineDispatchers(),
sessionStore = sessionStore,
userAgentProvider = SimpleUserAgentProvider(),
userCertificatesProvider = FakeUserCertificatesProvider(),
proxyProvider = FakeProxyProvider(),
clock = FakeSystemClock(),
utdTracker = UtdTracker(FakeAnalyticsService()),
featureFlagService = FakeFeatureFlagService(),
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
clientBuilderProvider = FakeClientBuilderProvider(),
)

View file

@ -0,0 +1,50 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClient
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustSyncService
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
import io.element.android.libraries.matrix.test.A_DEVICE_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.io.File
class RustMatrixClientTest {
@Test
fun `ensure that sessionId and deviceId can be retrieved from the client`() = runTest {
createRustMatrixClient().use { sut ->
assertThat(sut.sessionId).isEqualTo(A_USER_ID)
assertThat(sut.deviceId).isEqualTo(A_DEVICE_ID)
}
}
private fun TestScope.createRustMatrixClient(
sessionStore: SessionStore = InMemorySessionStore(),
) = RustMatrixClient(
client = FakeRustClient(),
baseDirectory = File(""),
sessionStore = sessionStore,
appCoroutineScope = this,
sessionDelegate = aRustClientSessionDelegate(
sessionStore = sessionStore,
),
syncService = FakeRustSyncService(),
dispatchers = testCoroutineDispatchers(),
baseCacheDirectory = File(""),
clock = FakeSystemClock(),
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
)
}

View file

@ -0,0 +1,17 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator
import io.element.android.libraries.matrix.test.A_PASSPHRASE
class FakePassphraseGenerator(
private val passphrase: () -> String? = { A_PASSPHRASE }
) : PassphraseGenerator {
override fun generatePassphrase(): String? = passphrase()
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.impl.proxy.ProxyProvider
class FakeProxyProvider : ProxyProvider {
override fun provides(): String? {
return null
}
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider
class FakeUserCertificatesProvider : UserCertificatesProvider {
override fun provides(): List<ByteArray> {
return emptyList()
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustHomeserverLoginDetails
import org.junit.Test
class HomeserverDetailsKtTest {
@Test
fun `map should be correct`() {
// Given
val homeserverLoginDetails = FakeRustHomeserverLoginDetails(
url = "https://example.org",
supportsPasswordLogin = true,
supportsOidcLogin = false
)
// When
val result = homeserverLoginDetails.map()
// Then
assertThat(result).isEqualTo(
MatrixHomeServerDetails(
url = "https://example.org",
supportsPasswordLogin = true,
supportsOidcLogin = false
)
)
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.OidcConfig
import org.junit.Test
import java.io.File
class OidcConfigurationProviderTest {
@Test
fun get() {
val result = OidcConfigurationProvider(File("/base")).get()
assertThat(result.redirectUri).isEqualTo(OidcConfig.REDIRECT_URI)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.auth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.impl.createRustMatrixClientFactory
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.io.File
class RustMatrixAuthenticationServiceTest {
@Test
fun `getLatestSessionId should return the value from the store`() = runTest {
val sessionStore = InMemorySessionStore()
val sut = createRustMatrixAuthenticationService(
sessionStore = sessionStore,
)
assertThat(sut.getLatestSessionId()).isNull()
sessionStore.storeData(aSessionData(sessionId = "@alice:server.org"))
assertThat(sut.getLatestSessionId()).isEqualTo(SessionId("@alice:server.org"))
}
private fun TestScope.createRustMatrixAuthenticationService(
sessionStore: SessionStore = InMemorySessionStore(),
): RustMatrixAuthenticationService {
val baseDirectory = File("/base")
val cacheDirectory = File("/cache")
val rustMatrixClientFactory = createRustMatrixClientFactory(
baseDirectory = baseDirectory,
cacheDirectory = cacheDirectory,
sessionStore = sessionStore,
)
return RustMatrixAuthenticationService(
sessionPathsFactory = SessionPathsFactory(baseDirectory, cacheDirectory),
coroutineDispatchers = testCoroutineDispatchers(),
sessionStore = sessionStore,
rustMatrixClientFactory = rustMatrixClientFactory,
passphraseGenerator = FakePassphraseGenerator(),
oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory),
appPreferencesStore = InMemoryAppPreferencesStore(),
)
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.factories
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineEvent
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_USER_NAME
import org.matrix.rustcomponents.sdk.NotificationEvent
import org.matrix.rustcomponents.sdk.NotificationItem
import org.matrix.rustcomponents.sdk.NotificationRoomInfo
import org.matrix.rustcomponents.sdk.NotificationSenderInfo
import org.matrix.rustcomponents.sdk.TimelineEvent
fun aRustNotificationItem(
event: NotificationEvent = aRustNotificationEventTimeline(),
senderInfo: NotificationSenderInfo = aRustNotificationSenderInfo(),
roomInfo: NotificationRoomInfo = aRustNotificationRoomInfo(),
isNoisy: Boolean? = false,
hasMention: Boolean? = false,
) = NotificationItem(
event = event,
senderInfo = senderInfo,
roomInfo = roomInfo,
isNoisy = isNoisy,
hasMention = hasMention,
)
fun aRustNotificationSenderInfo(
displayName: String? = A_USER_NAME,
avatarUrl: String? = null,
isNameAmbiguous: Boolean = false,
) = NotificationSenderInfo(
displayName = displayName,
avatarUrl = avatarUrl,
isNameAmbiguous = isNameAmbiguous,
)
fun aRustNotificationRoomInfo(
displayName: String = A_ROOM_NAME,
avatarUrl: String? = null,
canonicalAlias: String? = null,
joinedMembersCount: ULong = 2u,
isEncrypted: Boolean? = true,
isDirect: Boolean = false,
) = NotificationRoomInfo(
displayName = displayName,
avatarUrl = avatarUrl,
canonicalAlias = canonicalAlias,
joinedMembersCount = joinedMembersCount,
isEncrypted = isEncrypted,
isDirect = isDirect,
)
fun aRustNotificationEventTimeline(
event: TimelineEvent = FakeRustTimelineEvent(),
) = NotificationEvent.Timeline(
event = event,
)

View file

@ -12,15 +12,24 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID
import org.matrix.rustcomponents.sdk.PublicRoomJoinRule import org.matrix.rustcomponents.sdk.PublicRoomJoinRule
import org.matrix.rustcomponents.sdk.RoomDescription import org.matrix.rustcomponents.sdk.RoomDescription
internal fun aRustRoomDescription(): RoomDescription { internal fun aRustRoomDescription(
roomId: String = A_ROOM_ID.value,
name: String? = "name",
topic: String? = "topic",
alias: String? = A_ROOM_ALIAS.value,
avatarUrl: String? = "avatarUrl",
joinRule: PublicRoomJoinRule = PublicRoomJoinRule.PUBLIC,
isWorldReadable: Boolean = true,
joinedMembers: ULong = 2u,
): RoomDescription {
return RoomDescription( return RoomDescription(
roomId = A_ROOM_ID.value, roomId = roomId,
name = "name", name = name,
topic = "topic", topic = topic,
alias = A_ROOM_ALIAS.value, alias = alias,
avatarUrl = "avatarUrl", avatarUrl = avatarUrl,
joinRule = PublicRoomJoinRule.PUBLIC, joinRule = joinRule,
isWorldReadable = true, isWorldReadable = isWorldReadable,
joinedMembers = 2u joinedMembers = joinedMembers,
) )
} }

View file

@ -0,0 +1,19 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.factories
import org.matrix.rustcomponents.sdk.RoomNotificationMode
import org.matrix.rustcomponents.sdk.RoomNotificationSettings
fun aRustRoomNotificationSettings(
mode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
isDefault: Boolean = true,
) = RoomNotificationSettings(
mode = mode,
isDefault = isDefault
)

View file

@ -14,11 +14,13 @@ import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SlidingSyncVersion import org.matrix.rustcomponents.sdk.SlidingSyncVersion
internal fun aRustSession( internal fun aRustSession(
proxy: SlidingSyncVersion = SlidingSyncVersion.None proxy: SlidingSyncVersion = SlidingSyncVersion.None,
accessToken: String = "accessToken",
refreshToken: String = "refreshToken",
): Session { ): Session {
return Session( return Session(
accessToken = "accessToken", accessToken = accessToken,
refreshToken = "refreshToken", refreshToken = refreshToken,
userId = A_USER_ID.value, userId = A_USER_ID.value,
deviceId = A_DEVICE_ID.value, deviceId = A_DEVICE_ID.value,
homeserverUrl = A_HOMESERVER_URL, homeserverUrl = A_HOMESERVER_URL,

View file

@ -0,0 +1,45 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.factories
import io.element.android.libraries.matrix.test.A_MESSAGE
import org.matrix.rustcomponents.sdk.FormattedBody
import org.matrix.rustcomponents.sdk.MessageLikeEventContent
import org.matrix.rustcomponents.sdk.MessageType
import org.matrix.rustcomponents.sdk.TextMessageContent
import org.matrix.rustcomponents.sdk.TimelineEventType
fun aRustTimelineEventTypeMessageLike(
content: MessageLikeEventContent = aRustMessageLikeEventContentRoomMessage(),
): TimelineEventType.MessageLike {
return TimelineEventType.MessageLike(
content = content,
)
}
fun aRustMessageLikeEventContentRoomMessage(
messageType: MessageType = aRustMessageTypeText(),
inReplyToEventId: String? = null,
) = MessageLikeEventContent.RoomMessage(
messageType = messageType,
inReplyToEventId = inReplyToEventId,
)
fun aRustMessageTypeText(
content: TextMessageContent = aRustTextMessageContent(),
) = MessageType.Text(
content = content,
)
fun aRustTextMessageContent(
body: String = A_MESSAGE,
formatted: FormattedBody? = null,
) = TextMessageContent(
body = body,
formatted = formatted,
)

View file

@ -0,0 +1,37 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
import org.matrix.rustcomponents.sdk.TaskHandle
class FakeRoomDirectorySearch(
var isAtLastPage: Boolean = false,
) : RoomDirectorySearch(NoPointer) {
override suspend fun isAtLastPage(): Boolean {
return isAtLastPage
}
override suspend fun search(filter: String?, batchSize: UInt) = simulateLongTask { }
override suspend fun nextPage() = simulateLongTask { }
private var listener: RoomDirectorySearchEntriesListener? = null
override suspend fun results(listener: RoomDirectorySearchEntriesListener): TaskHandle {
this.listener = listener
return FakeRustTaskHandle()
}
fun emitResult(roomEntriesUpdate: List<RoomDirectorySearchEntryUpdate>) {
listener?.onUpdate(roomEntriesUpdate)
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession
import io.element.android.libraries.matrix.test.A_DEVICE_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientDelegate
import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
import org.matrix.rustcomponents.sdk.NotificationSettings
import org.matrix.rustcomponents.sdk.PusherIdentifiers
import org.matrix.rustcomponents.sdk.PusherKind
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.Session
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.TaskHandle
class FakeRustClient(
private val userId: String = A_USER_ID.value,
private val deviceId: String = A_DEVICE_ID.value,
private val notificationClient: NotificationClient = FakeRustNotificationClient(),
private val notificationSettings: NotificationSettings = FakeRustNotificationSettings(),
private val encryption: Encryption = FakeRustEncryption(),
private val session: Session = aRustSession(),
) : Client(NoPointer) {
override fun userId(): String = userId
override fun deviceId(): String = deviceId
override suspend fun notificationClient(processSetup: NotificationProcessSetup) = notificationClient
override fun getNotificationSettings(): NotificationSettings = notificationSettings
override fun encryption(): Encryption = encryption
override fun session(): Session = session
override fun setDelegate(delegate: ClientDelegate?): TaskHandle = FakeRustTaskHandle()
override fun cachedAvatarUrl(): String? = null
override suspend fun restoreSession(session: Session) = Unit
override fun syncService(): SyncServiceBuilder = FakeRustSyncServiceBuilder()
override fun roomDirectorySearch(): RoomDirectorySearch = FakeRoomDirectorySearch()
override suspend fun setPusher(
identifiers: PusherIdentifiers,
kind: PusherKind,
appDisplayName: String,
deviceDisplayName: String,
profileTag: String?,
lang: String,
) = Unit
override suspend fun deletePusher(identifiers: PusherIdentifiers) = Unit
}

View file

@ -0,0 +1,51 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientBuilder
import org.matrix.rustcomponents.sdk.ClientSessionDelegate
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.OidcConfiguration
import org.matrix.rustcomponents.sdk.QrCodeData
import org.matrix.rustcomponents.sdk.QrLoginProgressListener
import org.matrix.rustcomponents.sdk.RequestConfig
import org.matrix.rustcomponents.sdk.SlidingSyncVersionBuilder
import uniffi.matrix_sdk.BackupDownloadStrategy
import uniffi.matrix_sdk_crypto.CollectStrategy
class FakeRustClientBuilder : ClientBuilder(NoPointer) {
override fun addRootCertificates(certificates: List<ByteArray>) = this
override fun autoEnableBackups(autoEnableBackups: Boolean) = this
override fun autoEnableCrossSigning(autoEnableCrossSigning: Boolean) = this
override fun backupDownloadStrategy(backupDownloadStrategy: BackupDownloadStrategy) = this
override fun disableAutomaticTokenRefresh() = this
override fun disableBuiltInRootCertificates() = this
override fun disableSslVerification() = this
override fun enableCrossProcessRefreshLock(processId: String, sessionDelegate: ClientSessionDelegate) = this
override fun homeserverUrl(url: String) = this
override fun passphrase(passphrase: String?) = this
override fun proxy(url: String) = this
override fun requestConfig(config: RequestConfig) = this
override fun roomKeyRecipientStrategy(strategy: CollectStrategy) = this
override fun serverName(serverName: String) = this
override fun serverNameOrHomeserverUrl(serverNameOrUrl: String) = this
override fun sessionPaths(dataPath: String, cachePath: String) = this
override fun setSessionDelegate(sessionDelegate: ClientSessionDelegate) = this
override fun slidingSyncVersionBuilder(versionBuilder: SlidingSyncVersionBuilder) = this
override fun userAgent(userAgent: String) = this
override fun username(username: String) = this
override suspend fun buildWithQrCode(qrCodeData: QrCodeData, oidcConfiguration: OidcConfiguration, progressListener: QrLoginProgressListener): Client {
return FakeRustClient()
}
override suspend fun build(): Client {
return FakeRustClient()
}
}

View file

@ -0,0 +1,24 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.Encryption
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RecoveryStateListener
import org.matrix.rustcomponents.sdk.TaskHandle
import org.matrix.rustcomponents.sdk.VerificationStateListener
class FakeRustEncryption : Encryption(NoPointer) {
override fun verificationStateListener(listener: VerificationStateListener): TaskHandle {
return FakeRustTaskHandle()
}
override fun recoveryStateListener(listener: RecoveryStateListener): TaskHandle {
return FakeRustTaskHandle()
}
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.HomeserverLoginDetails
import org.matrix.rustcomponents.sdk.NoPointer
class FakeRustHomeserverLoginDetails(
private val url: String = "https://example.org",
private val supportsPasswordLogin: Boolean = true,
private val supportsOidcLogin: Boolean = false
) : HomeserverLoginDetails(NoPointer) {
override fun url(): String = url
override fun supportsOidcLogin(): Boolean = supportsOidcLogin
override fun supportsPasswordLogin(): Boolean = supportsPasswordLogin
}

View file

@ -0,0 +1,21 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.tests.testutils.simulateLongTask
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.NotificationItem
class FakeRustNotificationClient(
var notificationItemResult: NotificationItem? = null
) : NotificationClient(NoPointer) {
override suspend fun getNotification(roomId: String, eventId: String): NotificationItem? = simulateLongTask {
notificationItemResult
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomNotificationSettings
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.NotificationSettings
import org.matrix.rustcomponents.sdk.NotificationSettingsDelegate
import org.matrix.rustcomponents.sdk.RoomNotificationSettings
class FakeRustNotificationSettings(
private val roomNotificationSettings: RoomNotificationSettings = aRustRoomNotificationSettings(),
) : NotificationSettings(NoPointer) {
private var delegate: NotificationSettingsDelegate? = null
override fun setDelegate(delegate: NotificationSettingsDelegate?) {
this.delegate = delegate
}
override suspend fun getRoomNotificationSettings(
roomId: String,
isEncrypted: Boolean,
isOneToOne: Boolean,
): RoomNotificationSettings = roomNotificationSettings
}

View file

@ -0,0 +1,13 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomList
class FakeRustRoomList : RoomList(NoPointer)

View file

@ -0,0 +1,35 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener
import org.matrix.rustcomponents.sdk.TaskHandle
class FakeRustRoomListService : RoomListService(NoPointer) {
override suspend fun allRooms(): RoomList {
return FakeRustRoomList()
}
private var listener: RoomListServiceSyncIndicatorListener? = null
override fun syncIndicator(
delayBeforeShowingInMs: UInt,
delayBeforeHidingInMs: UInt,
listener: RoomListServiceSyncIndicatorListener,
): TaskHandle {
this.listener = listener
return FakeRustTaskHandle()
}
fun emitRoomListServiceSyncIndicator(syncIndicator: RoomListServiceSyncIndicator) {
listener?.onUpdate(syncIndicator)
}
}

View file

@ -0,0 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.RoomListService
import org.matrix.rustcomponents.sdk.SyncService
class FakeRustSyncService(
private val roomListService: RoomListService = FakeRustRoomListService(),
) : SyncService(NoPointer) {
override fun roomListService(): RoomListService = roomListService
}

View file

@ -0,0 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.SyncService
import org.matrix.rustcomponents.sdk.SyncServiceBuilder
import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate
class FakeRustSyncServiceBuilder : SyncServiceBuilder(NoPointer) {
override suspend fun withUtdHook(delegate: UnableToDecryptDelegate): SyncServiceBuilder = this
override suspend fun finish(): SyncService = FakeRustSyncService()
}

View file

@ -0,0 +1,16 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.TaskHandle
class FakeRustTaskHandle : TaskHandle(NoPointer) {
override fun cancel() = Unit
override fun destroy() = Unit
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustTimelineEventTypeMessageLike
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.TimelineEvent
import org.matrix.rustcomponents.sdk.TimelineEventType
class FakeRustTimelineEvent(
val timestamp: ULong = A_FAKE_TIMESTAMP.toULong(),
val timelineEventType: TimelineEventType = aRustTimelineEventTypeMessageLike(),
val senderId: String = A_USER_ID_2.value,
) : TimelineEvent(NoPointer) {
override fun timestamp(): ULong = timestamp
override fun eventType(): TimelineEventType = timelineEventType
override fun senderId(): String = senderId
}

View file

@ -0,0 +1,13 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.fixtures.fakes
import org.matrix.rustcomponents.sdk.NoPointer
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
class FakeRustTimelineEventTypeFilter : TimelineEventTypeFilter(NoPointer)

View file

@ -8,6 +8,7 @@
package io.element.android.libraries.matrix.impl.mapper package io.element.android.libraries.matrix.impl.mapper
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession import io.element.android.libraries.matrix.impl.fixtures.factories.aRustSession
import io.element.android.libraries.matrix.impl.paths.SessionPaths import io.element.android.libraries.matrix.impl.paths.SessionPaths
import io.element.android.libraries.matrix.test.A_DEVICE_ID import io.element.android.libraries.matrix.test.A_DEVICE_ID
@ -81,4 +82,54 @@ class SessionKtTest {
) )
assertThat(result.slidingSyncProxy).isEqualTo("proxyUrl") assertThat(result.slidingSyncProxy).isEqualTo("proxyUrl")
} }
@Test
fun `ExternalSession toSessionData compute the expected result`() {
val result = anExternalSession().toSessionData(
isTokenValid = true,
loginType = LoginType.PASSWORD,
passphrase = A_SECRET,
sessionPaths = SessionPaths(File("/a/file"), File("/a/cache")),
)
assertThat(result.userId).isEqualTo(A_USER_ID.value)
assertThat(result.deviceId).isEqualTo(A_DEVICE_ID.value)
assertThat(result.accessToken).isEqualTo("accessToken")
assertThat(result.refreshToken).isNull()
assertThat(result.homeserverUrl).isEqualTo(A_HOMESERVER_URL)
assertThat(result.isTokenValid).isTrue()
assertThat(result.oidcData).isNull()
assertThat(result.slidingSyncProxy).isNull()
assertThat(result.loginType).isEqualTo(LoginType.PASSWORD)
assertThat(result.loginTimestamp).isNotNull()
assertThat(result.passphrase).isEqualTo(A_SECRET)
assertThat(result.sessionPath).isEqualTo("/a/file")
assertThat(result.cachePath).isEqualTo("/a/cache")
}
@Test
fun `ExternalSession toSessionData can change the validity of the token`() {
val result = anExternalSession().toSessionData(
isTokenValid = false,
loginType = LoginType.PASSWORD,
passphrase = A_SECRET,
sessionPaths = SessionPaths(File("/a/file"), File("/a/cache")),
)
assertThat(result.isTokenValid).isFalse()
}
} }
private fun anExternalSession(
userId: String = A_USER_ID.value,
deviceId: String = A_DEVICE_ID.value,
accessToken: String = "accessToken",
refreshToken: String? = null,
homeserverUrl: String = A_HOMESERVER_URL,
slidingSyncProxy: String? = null,
) = ExternalSession(
userId = userId,
deviceId = deviceId,
accessToken = accessToken,
refreshToken = refreshToken,
homeserverUrl = homeserverUrl,
slidingSyncProxy = slidingSyncProxy,
)

View file

@ -0,0 +1,58 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.notification
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustNotificationItem
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustNotificationClient
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_MESSAGE
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.services.toolbox.api.systemclock.SystemClock
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.NotificationClient
class RustNotificationServiceTest {
@Test
fun test() = runTest {
val notificationClient = FakeRustNotificationClient(
notificationItemResult = aRustNotificationItem(),
)
val sut = createRustNotificationService(
notificationClient = notificationClient,
)
val result = sut.getNotification(A_ROOM_ID, AN_EVENT_ID).getOrThrow()!!
assertThat(result.isEncrypted).isTrue()
assertThat(result.content).isEqualTo(
NotificationContent.MessageLike.RoomMessage(
senderId = A_USER_ID_2,
messageType = TextMessageType(
body = A_MESSAGE,
formatted = null,
)
)
)
}
private fun TestScope.createRustNotificationService(
notificationClient: NotificationClient = FakeRustNotificationClient(),
clock: SystemClock = FakeSystemClock(),
) =
RustNotificationService(
notificationClient = notificationClient,
dispatchers = testCoroutineDispatchers(),
clock = clock,
)
}

View file

@ -0,0 +1,42 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.notificationsettings
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClient
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustNotificationSettings
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.NotificationSettings
class RustNotificationSettingsServiceTest {
@Test
fun test() = runTest {
val sut = createRustNotificationSettingsService()
val result = sut.getRoomNotificationSettings(
roomId = A_ROOM_ID,
isEncrypted = true,
isOneToOne = true,
).getOrNull()!!
assertThat(result.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES)
assertThat(result.isDefault).isTrue()
}
private fun TestScope.createRustNotificationSettingsService(
notificationSettings: NotificationSettings = FakeRustNotificationSettings(),
) = RustNotificationSettingsService(
client = FakeRustClient(
notificationSettings = notificationSettings,
),
dispatchers = testCoroutineDispatchers(),
)
}

View file

@ -0,0 +1,28 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.oidc
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.test.A_DEVICE_ID
import org.junit.Test
import org.matrix.rustcomponents.sdk.AccountManagementAction as RustAccountManagementAction
class AccountManagementActionKtTest {
@Test
fun `test AccountManagementAction to RustAccountManagementAction`() {
assertThat(AccountManagementAction.Profile.toRustAction())
.isEqualTo(RustAccountManagementAction.Profile)
assertThat(AccountManagementAction.SessionEnd(A_DEVICE_ID).toRustAction())
.isEqualTo(RustAccountManagementAction.SessionEnd(A_DEVICE_ID.value))
assertThat(AccountManagementAction.SessionView(A_DEVICE_ID).toRustAction())
.isEqualTo(RustAccountManagementAction.SessionView(A_DEVICE_ID.value))
assertThat(AccountManagementAction.SessionsList.toRustAction())
.isEqualTo(RustAccountManagementAction.SessionsList)
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.pushers
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClient
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.runTest
import org.junit.Test
class RustPushersServiceTest {
@Test
fun `setPusher should invoke the client method`() = runTest {
val sut = RustPushersService(
client = FakeRustClient(),
dispatchers = testCoroutineDispatchers()
)
sut.setHttpPusher(
setHttpPusherData = aSetHttpPusherData()
).getOrThrow()
}
@Test
fun `unsetPusher should invoke the client method`() = runTest {
val sut = RustPushersService(
client = FakeRustClient(),
dispatchers = testCoroutineDispatchers()
)
sut.unsetHttpPusher(
unsetHttpPusherData = aUnsetHttpPusherData(),
).getOrThrow()
}
}
private fun aSetHttpPusherData(
pushKey: String = "pushKey",
appId: String = "appId",
url: String = "url",
defaultPayload: String = "defaultPayload",
appDisplayName: String = "appDisplayName",
deviceDisplayName: String = "deviceDisplayName",
profileTag: String = "profileTag",
lang: String = "lang",
) = SetHttpPusherData(
pushKey = pushKey,
appId = appId,
url = url,
defaultPayload = defaultPayload,
appDisplayName = appDisplayName,
deviceDisplayName = deviceDisplayName,
profileTag = profileTag,
lang = lang
)
private fun aUnsetHttpPusherData(
pushKey: String = "pushKey",
appId: String = "appId",
) = UnsetHttpPusherData(
pushKey = pushKey,
appId = appId,
)

View file

@ -0,0 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.room
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustTimelineEventTypeFilter
import org.matrix.rustcomponents.sdk.TimelineEventTypeFilter
class FakeTimelineEventTypeFilterFactory : TimelineEventTypeFilterFactory {
override fun create(listStateEventType: List<StateEventType>): TimelineEventTypeFilter {
return FakeRustTimelineEventTypeFilter()
}
}

View file

@ -0,0 +1,74 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomdirectory
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomDescription
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID_3
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
class RoomDirectorySearchProcessorTest {
private val rustRoom1 = aRustRoomDescription(roomId = A_ROOM_ID.value)
private val rustRoom2 = aRustRoomDescription(roomId = A_ROOM_ID_2.value)
private val rustRoom3 = aRustRoomDescription(roomId = A_ROOM_ID_3.value)
private val mapper = RoomDescriptionMapper()
private val room1 = mapper.map(rustRoom1)
private val room2 = mapper.map(rustRoom2)
private val room3 = mapper.map(rustRoom3)
@Test
fun test() = runTest {
val sut = RoomDirectorySearchProcessor(
coroutineContext = StandardTestDispatcher(testScheduler),
)
sut.roomDescriptionsFlow.test {
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Reset(listOf(rustRoom1))))
assertThat(awaitItem()).isEqualTo(listOf(room1))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Append(listOf(rustRoom2))))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.PushFront(rustRoom3)))
assertThat(awaitItem()).isEqualTo(listOf(room3, room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.PopFront))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.PushBack(rustRoom3)))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2, room3))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.PopBack))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Insert(1u, rustRoom3)))
assertThat(awaitItem()).isEqualTo(listOf(room1, room3, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Remove(1u)))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Reset(listOf(rustRoom1, rustRoom2))))
assertThat(awaitItem()).isEqualTo(listOf(room1, room2))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Set(1u, rustRoom3)))
assertThat(awaitItem()).isEqualTo(listOf(room1, room3))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Truncate(1u)))
assertThat(awaitItem()).isEqualTo(listOf(room1))
sut.postUpdates(listOf(RoomDirectorySearchEntryUpdate.Clear))
assertThat(awaitItem()).isEmpty()
// Check that all the actions are performed
sut.postUpdates(
listOf(
RoomDirectorySearchEntryUpdate.PushBack(rustRoom1),
RoomDirectorySearchEntryUpdate.PushBack(rustRoom2),
RoomDirectorySearchEntryUpdate.PushBack(rustRoom3),
)
)
assertThat(awaitItem()).isEqualTo(listOf(room1, room2, room3))
}
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomdirectory
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomDescription
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRoomDirectorySearch
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomDirectorySearch
import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate
@OptIn(ExperimentalCoroutinesApi::class)
class RustRoomDirectoryListTest {
@Test
fun `check that the state emits the expected values`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
val fakeRoomDirectorySearch = FakeRoomDirectorySearch()
val mapper = RoomDescriptionMapper()
val sut = testScope.createRustRoomDirectoryList(
roomDirectorySearch = fakeRoomDirectorySearch,
scope = cancellableScope,
)
// Let the mxCallback be ready
testScope.runCurrent()
sut.state.test {
sut.filter("", 20)
fakeRoomDirectorySearch.emitResult(
listOf(
RoomDirectorySearchEntryUpdate.Append(listOf(aRustRoomDescription()))
)
)
val initialItem = awaitItem()
assertThat(initialItem).isEqualTo(
RoomDirectoryList.State(
hasMoreToLoad = true,
items = listOf(mapper.map(aRustRoomDescription()))
)
)
assertThat(initialItem.hasMoreToLoad).isTrue()
fakeRoomDirectorySearch.isAtLastPage = true
sut.loadMore()
fakeRoomDirectorySearch.emitResult(
listOf(
RoomDirectorySearchEntryUpdate.Append(listOf(aRustRoomDescription(A_ROOM_ID_2.value)))
)
)
val nextItem = awaitItem()
assertThat(nextItem).isEqualTo(
RoomDirectoryList.State(
hasMoreToLoad = false,
items = listOf(
mapper.map(aRustRoomDescription()),
)
)
)
val finalItem = awaitItem()
assertThat(finalItem).isEqualTo(
RoomDirectoryList.State(
hasMoreToLoad = false,
items = listOf(
mapper.map(aRustRoomDescription()),
mapper.map(aRustRoomDescription(A_ROOM_ID_2.value)),
)
)
)
}
}
private fun TestScope.createRustRoomDirectoryList(
roomDirectorySearch: RoomDirectorySearch = FakeRoomDirectorySearch(),
scope: CoroutineScope,
) = RustRoomDirectoryList(
inner = roomDirectorySearch,
coroutineScope = scope,
coroutineContext = StandardTestDispatcher(testScheduler),
)
}

View file

@ -0,0 +1,25 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomdirectory
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustClient
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Test
class RustRoomDirectoryServiceTest {
@Test
fun test() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
val client = FakeRustClient()
val sut = RustRoomDirectoryService(
client = client,
sessionDispatcher = StandardTestDispatcher(testScope.testScheduler),
)
sut.createRoomDirectoryList(cancellableScope)
}
}

View file

@ -0,0 +1,30 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomlist
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomList
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.tests.testutils.runCancellableScopeTest
import org.junit.Test
import kotlin.coroutines.EmptyCoroutineContext
class RoomListFactoryTest {
@Test
fun `createRoomList should work`() = runCancellableScopeTest {
val sut = RoomListFactory(
innerRoomListService = FakeRustRoomListService(),
sessionCoroutineScope = it,
)
sut.createRoomList(
pageSize = 10,
coroutineContext = EmptyCoroutineContext,
) {
FakeRustRoomList()
}
}
}

View file

@ -8,9 +8,9 @@
package io.element.android.libraries.matrix.impl.roomlist package io.element.android.libraries.matrix.impl.roomlist
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.sun.jna.Pointer
import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListItem import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListItem
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID_2 import io.element.android.libraries.matrix.test.A_ROOM_ID_2
import io.element.android.libraries.matrix.test.A_ROOM_ID_3 import io.element.android.libraries.matrix.test.A_ROOM_ID_3
@ -21,17 +21,8 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomList
import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate import org.matrix.rustcomponents.sdk.RoomListEntriesUpdate
import org.matrix.rustcomponents.sdk.RoomListItem
import org.matrix.rustcomponents.sdk.RoomListServiceInterface
import org.matrix.rustcomponents.sdk.RoomListServiceStateListener
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicatorListener
import org.matrix.rustcomponents.sdk.RoomSubscription
import org.matrix.rustcomponents.sdk.TaskHandle
// NOTE: this class is using a fake implementation of a Rust SDK interface which returns actual Rust objects with pointers.
// Since we don't access the data in those objects, this is fine for our tests, but that's as far as we can test this class.
class RoomSummaryListProcessorTest { class RoomSummaryListProcessorTest {
private val summaries = MutableStateFlow<List<RoomSummary>>(emptyList()) private val summaries = MutableStateFlow<List<RoomSummary>>(emptyList())
@ -163,29 +154,8 @@ class RoomSummaryListProcessorTest {
private fun TestScope.createProcessor() = RoomSummaryListProcessor( private fun TestScope.createProcessor() = RoomSummaryListProcessor(
summaries, summaries,
fakeRoomListService, FakeRustRoomListService(),
coroutineContext = StandardTestDispatcher(testScheduler), coroutineContext = StandardTestDispatcher(testScheduler),
roomSummaryDetailsFactory = RoomSummaryDetailsFactory(), roomSummaryDetailsFactory = RoomSummaryDetailsFactory(),
) )
// Fake room list service that returns Rust objects with null pointers. Luckily for us, they don't crash for our test cases
private val fakeRoomListService = object : RoomListServiceInterface {
override suspend fun allRooms(): RoomList {
return RoomList(Pointer.NULL)
}
override fun room(roomId: String): RoomListItem {
return RoomListItem(Pointer.NULL)
}
override fun state(listener: RoomListServiceStateListener): TaskHandle {
return TaskHandle(Pointer.NULL)
}
override fun syncIndicator(delayBeforeShowingInMs: UInt, delayBeforeHidingInMs: UInt, listener: RoomListServiceSyncIndicatorListener): TaskHandle {
return TaskHandle(Pointer.NULL)
}
override fun subscribeToRooms(roomIds: List<String>, settings: RoomSubscription?) = Unit
}
} }

View file

@ -0,0 +1,61 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only
* Please see LICENSE in the repository root for full details.
*/
package io.element.android.libraries.matrix.impl.roomlist
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRustRoomListService
import io.element.android.libraries.matrix.impl.room.RoomSyncSubscriber
import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Test
import org.matrix.rustcomponents.sdk.RoomListServiceSyncIndicator
import org.matrix.rustcomponents.sdk.RoomListService as RustRoomListService
@OptIn(ExperimentalCoroutinesApi::class)
class RustRoomListServiceTest {
@Test
fun `syncIndicator should emit the expected values`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope ->
val roomListService = FakeRustRoomListService()
val sut = testScope.createRustRoomListService(
sessionCoroutineScope = cancellableScope,
roomListService = roomListService,
)
// Give time for mxCallback to setup
testScope.runCurrent()
sut.syncIndicator.test {
assertThat(awaitItem()).isEqualTo(RoomListService.SyncIndicator.Hide)
roomListService.emitRoomListServiceSyncIndicator(RoomListServiceSyncIndicator.SHOW)
assertThat(awaitItem()).isEqualTo(RoomListService.SyncIndicator.Show)
roomListService.emitRoomListServiceSyncIndicator(RoomListServiceSyncIndicator.HIDE)
assertThat(awaitItem()).isEqualTo(RoomListService.SyncIndicator.Hide)
}
}
}
private fun TestScope.createRustRoomListService(
sessionCoroutineScope: CoroutineScope,
roomListService: RustRoomListService = FakeRustRoomListService(),
) = RustRoomListService(
innerRoomListService = roomListService,
sessionDispatcher = StandardTestDispatcher(testScheduler),
roomListFactory = RoomListFactory(
innerRoomListService = roomListService,
sessionCoroutineScope = sessionCoroutineScope,
),
roomSyncSubscriber = RoomSyncSubscriber(
roomListService = roomListService,
dispatchers = testCoroutineDispatchers(),
),
sessionCoroutineScope = sessionCoroutineScope,
)

View file

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
const val A_USER_NAME = "alice" const val A_USER_NAME = "alice"
const val A_PASSWORD = "password" const val A_PASSWORD = "password"
const val A_PASSPHRASE = "passphrase"
const val A_SECRET = "secret" const val A_SECRET = "secret"
val A_USER_ID = UserId("@alice:server.org") val A_USER_ID = UserId("@alice:server.org")

View file

@ -9,7 +9,6 @@ package io.element.android.libraries.matrix.test.notification
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
@ -21,7 +20,6 @@ class FakeNotificationService : NotificationService {
} }
override suspend fun getNotification( override suspend fun getNotification(
userId: SessionId,
roomId: RoomId, roomId: RoomId,
eventId: EventId, eventId: EventId,
): Result<NotificationData?> { ): Result<NotificationData?> {

View file

@ -24,7 +24,6 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.notification.NotificationContent import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType import io.element.android.libraries.matrix.api.timeline.item.event.EmoteMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.EventType import io.element.android.libraries.matrix.api.timeline.item.event.EventType
@ -76,7 +75,6 @@ class DefaultNotifiableEventResolver @Inject constructor(
val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null val client = matrixClientProvider.getOrRestore(sessionId).getOrNull() ?: return null
val notificationService = client.notificationService() val notificationService = client.notificationService()
val notificationData = notificationService.getNotification( val notificationData = notificationService.getNotification(
userId = sessionId,
roomId = roomId, roomId = roomId,
eventId = eventId, eventId = eventId,
).onFailure { ).onFailure {
@ -113,30 +111,26 @@ class DefaultNotifiableEventResolver @Inject constructor(
hasMentionOrReply = hasMention, hasMentionOrReply = hasMention,
) )
} }
is NotificationContent.StateEvent.RoomMemberContent -> { is NotificationContent.Invite -> {
if (content.membershipState == RoomMembershipState.INVITE) { InviteNotifiableEvent(
InviteNotifiableEvent( sessionId = userId,
sessionId = userId, roomId = roomId,
roomId = roomId, eventId = eventId,
eventId = eventId, editedEventId = null,
editedEventId = null, canBeReplaced = true,
canBeReplaced = true, roomName = roomDisplayName,
roomName = roomDisplayName, noisy = isNoisy,
noisy = isNoisy, timestamp = this.timestamp,
timestamp = this.timestamp, soundName = null,
soundName = null, isRedacted = false,
isRedacted = false, isUpdated = false,
isUpdated = false, // TODO We could use the senderId here
description = descriptionFromRoomMembershipInvite(isDirect), description = descriptionFromRoomMembershipInvite(isDirect),
// TODO check if type is needed anymore // TODO check if type is needed anymore
type = null, type = null,
// TODO check if title is needed anymore // TODO check if title is needed anymore
title = null, title = null,
) )
} else {
Timber.tag(loggerTag.value).d("Ignoring notification state event for membership ${content.membershipState}")
null
}
} }
NotificationContent.MessageLike.CallAnswer, NotificationContent.MessageLike.CallAnswer,
NotificationContent.MessageLike.CallCandidates, NotificationContent.MessageLike.CallCandidates,
@ -203,6 +197,7 @@ class DefaultNotifiableEventResolver @Inject constructor(
NotificationContent.MessageLike.Sticker -> null.also { NotificationContent.MessageLike.Sticker -> null.also {
Timber.tag(loggerTag.value).d("Ignoring notification for sticker") Timber.tag(loggerTag.value).d("Ignoring notification for sticker")
} }
is NotificationContent.StateEvent.RoomMemberContent,
NotificationContent.StateEvent.PolicyRuleRoom, NotificationContent.StateEvent.PolicyRuleRoom,
NotificationContent.StateEvent.PolicyRuleServer, NotificationContent.StateEvent.PolicyRuleServer,
NotificationContent.StateEvent.PolicyRuleUser, NotificationContent.StateEvent.PolicyRuleUser,

View file

@ -30,7 +30,7 @@ class DefaultOnMissedCallNotificationHandler @Inject constructor(
// Resolve the event and add a notification for it, at this point it should no longer be a ringing one // Resolve the event and add a notification for it, at this point it should no longer be a ringing one
val notificationData = matrixClientProvider.getOrRestore(sessionId).getOrNull() val notificationData = matrixClientProvider.getOrRestore(sessionId).getOrNull()
?.notificationService() ?.notificationService()
?.getNotification(sessionId, roomId, eventId) ?.getNotification(roomId, eventId)
?.getOrNull() ?.getOrNull()
?: return ?: return

View file

@ -370,7 +370,7 @@ class DefaultNotifiableEventResolverTest {
notificationResult = Result.success( notificationResult = Result.success(
createNotificationData( createNotificationData(
content = NotificationContent.StateEvent.RoomMemberContent( content = NotificationContent.StateEvent.RoomMemberContent(
userId = A_USER_ID_2.value, userId = A_USER_ID_2,
membershipState = RoomMembershipState.INVITE membershipState = RoomMembershipState.INVITE
), ),
isDirect = false, isDirect = false,
@ -378,6 +378,22 @@ class DefaultNotifiableEventResolverTest {
) )
) )
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID) val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
assertThat(result).isNull()
}
@Test
fun `resolve invite room`() = runTest {
val sut = createDefaultNotifiableEventResolver(
notificationResult = Result.success(
createNotificationData(
content = NotificationContent.Invite(
senderId = A_USER_ID_2,
),
isDirect = false,
)
)
)
val result = sut.resolveEvent(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID)
val expectedResult = ResolvedPushEvent.Event( val expectedResult = ResolvedPushEvent.Event(
InviteNotifiableEvent( InviteNotifiableEvent(
sessionId = A_SESSION_ID, sessionId = A_SESSION_ID,
@ -400,13 +416,12 @@ class DefaultNotifiableEventResolverTest {
} }
@Test @Test
fun `resolve RoomMemberContent invite direct`() = runTest { fun `resolve invite invite direct`() = runTest {
val sut = createDefaultNotifiableEventResolver( val sut = createDefaultNotifiableEventResolver(
notificationResult = Result.success( notificationResult = Result.success(
createNotificationData( createNotificationData(
content = NotificationContent.StateEvent.RoomMemberContent( content = NotificationContent.Invite(
userId = A_USER_ID_2.value, senderId = A_USER_ID_2,
membershipState = RoomMembershipState.INVITE
), ),
isDirect = true, isDirect = true,
) )
@ -440,7 +455,7 @@ class DefaultNotifiableEventResolverTest {
notificationResult = Result.success( notificationResult = Result.success(
createNotificationData( createNotificationData(
content = NotificationContent.StateEvent.RoomMemberContent( content = NotificationContent.StateEvent.RoomMemberContent(
userId = A_USER_ID_2.value, userId = A_USER_ID_2,
membershipState = RoomMembershipState.JOIN membershipState = RoomMembershipState.JOIN
) )
) )

View file

@ -16,12 +16,14 @@ fun aSessionData(
isTokenValid: Boolean = false, isTokenValid: Boolean = false,
sessionPath: String = "/a/path/to/a/session", sessionPath: String = "/a/path/to/a/session",
cachePath: String = "/a/path/to/a/cache", cachePath: String = "/a/path/to/a/cache",
): SessionData { accessToken: String = "anAccessToken",
refreshToken: String? = "aRefreshToken",
): SessionData {
return SessionData( return SessionData(
userId = sessionId, userId = sessionId,
deviceId = deviceId, deviceId = deviceId,
accessToken = "anAccessToken", accessToken = accessToken,
refreshToken = "aRefreshToken", refreshToken = refreshToken,
homeserverUrl = "aHomeserverUrl", homeserverUrl = "aHomeserverUrl",
oidcData = null, oidcData = null,
slidingSyncProxy = null, slidingSyncProxy = null,

View file

@ -19,11 +19,13 @@ import androidx.compose.ui.platform.LocalContext
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.impl.RustClientBuilderProvider
import io.element.android.libraries.matrix.impl.RustMatrixClientFactory import io.element.android.libraries.matrix.impl.RustMatrixClientFactory
import io.element.android.libraries.matrix.impl.analytics.UtdTracker import io.element.android.libraries.matrix.impl.analytics.UtdTracker
import io.element.android.libraries.matrix.impl.auth.OidcConfigurationProvider import io.element.android.libraries.matrix.impl.auth.OidcConfigurationProvider
import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService
import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory
import io.element.android.libraries.matrix.impl.room.RustTimelineEventTypeFilterFactory
import io.element.android.libraries.network.useragent.SimpleUserAgentProvider import io.element.android.libraries.network.useragent.SimpleUserAgentProvider
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.LoggedInState
@ -56,6 +58,8 @@ class MainActivity : ComponentActivity() {
clock = DefaultSystemClock(), clock = DefaultSystemClock(),
utdTracker = UtdTracker(NoopAnalyticsService()), utdTracker = UtdTracker(NoopAnalyticsService()),
featureFlagService = AlwaysEnabledFeatureFlagService(), featureFlagService = AlwaysEnabledFeatureFlagService(),
timelineEventTypeFilterFactory = RustTimelineEventTypeFilterFactory(),
clientBuilderProvider = RustClientBuilderProvider(),
), ),
passphraseGenerator = NullPassphraseGenerator(), passphraseGenerator = NullPassphraseGenerator(),
oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory), oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory),

View file

@ -10,6 +10,7 @@ package io.element.android.tests.testutils
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
/** /**
@ -20,3 +21,12 @@ fun runCancellableScopeTest(block: suspend (CoroutineScope) -> Unit) = runTest {
block(scope) block(scope)
scope.cancel() scope.cancel()
} }
/**
* Run a test with a [CoroutineScope] that will be cancelled automatically and avoiding failing the test.
*/
fun runCancellableScopeTestWithTestScope(block: suspend (testScope: TestScope, cancellableScope: CoroutineScope) -> Unit) = runTest {
val scope = CoroutineScope(coroutineContext + SupervisorJob())
block(this, scope)
scope.cancel()
}