Merge pull request #4824 from element-hq/feature/fga/fix_identity_change
fix (identity change) : RoomMemberIdentityStateChange in non encrypted room
This commit is contained in:
@@ -15,7 +15,7 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.ui.room.observeRoomMemberIdentityStateChange
|
||||
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -30,7 +30,7 @@ class IdentityChangeStatePresenter @Inject constructor(
|
||||
override fun present(): IdentityChangeState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val roomMemberIdentityStateChange by produceState(persistentListOf()) {
|
||||
observeRoomMemberIdentityStateChange(room)
|
||||
room.roomMemberIdentityStateChange(waitForEncryption = true).collect { value = it }
|
||||
}
|
||||
|
||||
fun handleEvent(event: IdentityChangeEvent) {
|
||||
|
||||
@@ -174,7 +174,7 @@ class RoomDetailsPresenter @Inject constructor(
|
||||
}
|
||||
|
||||
val hasMemberVerificationViolations by produceState(false) {
|
||||
room.roomMemberIdentityStateChange()
|
||||
room.roomMemberIdentityStateChange(waitForEncryption = true)
|
||||
.onEach { identities -> value = identities.any { it.identityState == IdentityState.VerificationViolation } }
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class RoomMemberListPresenter @Inject constructor(
|
||||
val roomModerationState = roomMembersModerationPresenter.present()
|
||||
|
||||
val roomMemberIdentityStates by produceState(persistentMapOf<UserId, IdentityState>()) {
|
||||
room.roomMemberIdentityStateChange()
|
||||
room.roomMemberIdentityStateChange(waitForEncryption = true)
|
||||
.onEach { identities ->
|
||||
value = identities.associateBy({ it.identityRoomMember.userId }, { it.identityState }).toPersistentMap()
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ package io.element.android.features.roomdetails.impl.members.details
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -33,9 +34,7 @@ import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
|
||||
import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -86,31 +85,30 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||
|
||||
val userProfileState = userProfilePresenter.present()
|
||||
|
||||
val identityStateChanges by produceState<IdentityStateChange?>(initialValue = null) {
|
||||
room.roomInfoFlow.filter { it.isEncrypted == true }
|
||||
.flatMapLatest {
|
||||
// Fetch the initial identity state manually
|
||||
val identityState = encryptionService.getUserIdentity(roomMemberId).getOrNull()
|
||||
value = identityState?.let { IdentityStateChange(roomMemberId, it) }
|
||||
val identityStateChanges = produceState<IdentityStateChange?>(initialValue = null) {
|
||||
// Fetch the initial identity state manually
|
||||
val identityState = encryptionService.getUserIdentity(roomMemberId).getOrNull()
|
||||
value = identityState?.let { IdentityStateChange(roomMemberId, it) }
|
||||
|
||||
// Subscribe to the identity changes
|
||||
room.roomMemberIdentityStateChange()
|
||||
.map { it.find { it.identityRoomMember.userId == roomMemberId } }
|
||||
.map { roomMemberIdentityStateChange ->
|
||||
// If we didn't receive any info, manually fetch it
|
||||
roomMemberIdentityStateChange?.identityState ?: encryptionService.getUserIdentity(roomMemberId).getOrNull()
|
||||
}
|
||||
.filterNotNull()
|
||||
// Subscribe to the identity changes
|
||||
room.roomMemberIdentityStateChange(waitForEncryption = false)
|
||||
.map { it.find { it.identityRoomMember.userId == roomMemberId } }
|
||||
.map { roomMemberIdentityStateChange ->
|
||||
// If we didn't receive any info, manually fetch it
|
||||
roomMemberIdentityStateChange?.identityState ?: encryptionService.getUserIdentity(roomMemberId).getOrNull()
|
||||
}
|
||||
.filterNotNull()
|
||||
.collect { value = IdentityStateChange(roomMemberId, it) }
|
||||
}
|
||||
|
||||
val verificationState = remember(identityStateChanges) {
|
||||
when (identityStateChanges?.identityState) {
|
||||
IdentityState.VerificationViolation -> UserProfileVerificationState.VERIFICATION_VIOLATION
|
||||
IdentityState.Verified -> UserProfileVerificationState.VERIFIED
|
||||
IdentityState.Pinned, IdentityState.PinViolation -> UserProfileVerificationState.UNVERIFIED
|
||||
else -> UserProfileVerificationState.UNKNOWN
|
||||
val verificationState by remember {
|
||||
derivedStateOf {
|
||||
when (identityStateChanges.value?.identityState) {
|
||||
IdentityState.VerificationViolation -> UserProfileVerificationState.VERIFICATION_VIOLATION
|
||||
IdentityState.Verified -> UserProfileVerificationState.VERIFIED
|
||||
IdentityState.Pinned, IdentityState.PinViolation -> UserProfileVerificationState.UNVERIFIED
|
||||
else -> UserProfileVerificationState.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.libraries.matrix.ui.room
|
||||
|
||||
import androidx.compose.runtime.ProduceStateScope
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -17,25 +16,25 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.roomMembers
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun JoinedRoom.roomMemberIdentityStateChange(): Flow<ImmutableList<RoomMemberIdentityStateChange>> {
|
||||
return roomInfoFlow
|
||||
.filter {
|
||||
// Room cannot become unencrypted, so we can just apply a filter here.
|
||||
it.isEncrypted == true
|
||||
fun JoinedRoom.roomMemberIdentityStateChange(waitForEncryption: Boolean): Flow<ImmutableList<RoomMemberIdentityStateChange>> {
|
||||
val encryptionChangeFlow = flow {
|
||||
if (waitForEncryption) {
|
||||
// Room cannot become unencrypted, so it's ok to use first here
|
||||
roomInfoFlow.first { roomInfo -> roomInfo.isEncrypted == true }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
emit(Unit)
|
||||
}
|
||||
return encryptionChangeFlow
|
||||
.flatMapLatest {
|
||||
combine(identityStateChangesFlow, membersStateFlow) { identityStateChanges, membersState ->
|
||||
identityStateChanges.map { identityStateChange ->
|
||||
@@ -52,14 +51,6 @@ fun JoinedRoom.roomMemberIdentityStateChange(): Flow<ImmutableList<RoomMemberIde
|
||||
}
|
||||
}
|
||||
|
||||
fun ProduceStateScope<PersistentList<RoomMemberIdentityStateChange>>.observeRoomMemberIdentityStateChange(room: JoinedRoom) {
|
||||
room.roomMemberIdentityStateChange()
|
||||
.onEach { roomMemberIdentityStateChanges ->
|
||||
value = roomMemberIdentityStateChanges.toPersistentList()
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
private fun RoomMember.toIdentityRoomMember() = IdentityRoomMember(
|
||||
userId = userId,
|
||||
displayNameOrDefault = displayNameOrDefault,
|
||||
|
||||
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.matrix.ui.room
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembersState
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class ObserveRoomMemberIdentityStateChangeTest {
|
||||
private val aliceRoomMember = aRoomMember(A_USER_ID, displayName = "Alice")
|
||||
private val bobRoomMember = aRoomMember(A_USER_ID_2, displayName = "Bob")
|
||||
private val carolRoomMember = aRoomMember(A_USER_ID_3, displayName = "Carol")
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange emits empty list for non-encrypted room with no identity changes`() =
|
||||
runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow<List<IdentityStateChange>>(emptyList())
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = false))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val result = awaitItem()
|
||||
assertThat(result).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange emits identity changes for non-encrypted room when waitForEncryption is false`() =
|
||||
runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.Verified),
|
||||
IdentityStateChange(carolRoomMember.userId, IdentityState.PinViolation)
|
||||
)
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = false))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember,
|
||||
carolRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(2)
|
||||
|
||||
val bobChange = result.find { it.identityRoomMember.userId == bobRoomMember.userId }
|
||||
assertThat(bobChange).isNotNull()
|
||||
assertThat(bobChange?.identityState).isEqualTo(IdentityState.Verified)
|
||||
assertThat(bobChange?.identityRoomMember?.displayNameOrDefault).isEqualTo("Bob")
|
||||
|
||||
val carolChange = result.find { it.identityRoomMember.userId == carolRoomMember.userId }
|
||||
assertThat(carolChange).isNotNull()
|
||||
assertThat(carolChange?.identityState).isEqualTo(IdentityState.PinViolation)
|
||||
assertThat(carolChange?.identityRoomMember?.displayNameOrDefault).isEqualTo("Carol")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange emits identity changes for already encrypted room`() =
|
||||
runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.VerificationViolation)
|
||||
)
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = true).test {
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(1)
|
||||
|
||||
val bobChange = result.first()
|
||||
assertThat(bobChange.identityRoomMember.userId).isEqualTo(bobRoomMember.userId)
|
||||
assertThat(bobChange.identityState).isEqualTo(IdentityState.VerificationViolation)
|
||||
assertThat(bobChange.identityRoomMember.displayNameOrDefault).isEqualTo("Bob")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange waits for encryption before emitting when waitForEncryption is true`() =
|
||||
runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(IdentityStateChange(bobRoomMember.userId, IdentityState.Pinned))
|
||||
)
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = false))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = true).test {
|
||||
// Should not emit anything yet since room is not encrypted
|
||||
expectNoEvents()
|
||||
|
||||
// Enable encryption
|
||||
joinedRoom.baseRoom.givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result.first().identityRoomMember.userId).isEqualTo(bobRoomMember.userId)
|
||||
assertThat(result.first().identityState).isEqualTo(IdentityState.Pinned)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange creates default member when room member not found`() =
|
||||
runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(IdentityStateChange(carolRoomMember.userId, IdentityState.PinViolation))
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
// Only include aliceRoomMember and bobRoomMember, not carolRoomMember
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(1)
|
||||
|
||||
val carolChange = result.first()
|
||||
assertThat(carolChange.identityRoomMember.userId).isEqualTo(carolRoomMember.userId)
|
||||
assertThat(carolChange.identityState).isEqualTo(IdentityState.PinViolation)
|
||||
// Should use extracted display name from user ID since member not found
|
||||
assertThat(carolChange.identityRoomMember.displayNameOrDefault).isEqualTo(
|
||||
carolRoomMember.userId.extractedDisplayName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange updates when identity state changes`() = runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(IdentityStateChange(bobRoomMember.userId, IdentityState.Pinned))
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val firstResult = awaitItem()
|
||||
assertThat(firstResult).hasSize(1)
|
||||
assertThat(firstResult.first().identityState).isEqualTo(IdentityState.Pinned)
|
||||
|
||||
// Update identity state
|
||||
identityStateChangesFlow.value = listOf(
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.VerificationViolation)
|
||||
)
|
||||
|
||||
val secondResult = awaitItem()
|
||||
assertThat(secondResult).hasSize(1)
|
||||
assertThat(secondResult.first().identityState).isEqualTo(IdentityState.VerificationViolation)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange updates when members state changes`() = runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(IdentityStateChange(bobRoomMember.userId, IdentityState.Verified))
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val firstResult = awaitItem()
|
||||
assertThat(firstResult).hasSize(1)
|
||||
assertThat(firstResult.first().identityRoomMember.displayNameOrDefault).isEqualTo("Bob")
|
||||
|
||||
// Update room member with different display name
|
||||
val updatedMember2 = bobRoomMember.copy(displayName = "Bobby")
|
||||
joinedRoom.baseRoom.givenRoomMembersState(
|
||||
RoomMembersState.Ready(persistentListOf(aliceRoomMember, updatedMember2))
|
||||
)
|
||||
|
||||
val secondResult = awaitItem()
|
||||
assertThat(secondResult).hasSize(1)
|
||||
assertThat(secondResult.first().identityRoomMember.displayNameOrDefault).isEqualTo("Bobby")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange handles multiple identity states`() = runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(
|
||||
IdentityStateChange(aliceRoomMember.userId, IdentityState.Verified),
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.PinViolation),
|
||||
IdentityStateChange(carolRoomMember.userId, IdentityState.VerificationViolation)
|
||||
)
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember,
|
||||
carolRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(3)
|
||||
|
||||
val verifiedUser = result.find { it.identityState == IdentityState.Verified }
|
||||
assertThat(verifiedUser?.identityRoomMember?.userId).isEqualTo(aliceRoomMember.userId)
|
||||
|
||||
val pinViolationUser = result.find { it.identityState == IdentityState.PinViolation }
|
||||
assertThat(pinViolationUser?.identityRoomMember?.userId).isEqualTo(bobRoomMember.userId)
|
||||
|
||||
val verificationViolationUser =
|
||||
result.find { it.identityState == IdentityState.VerificationViolation }
|
||||
assertThat(verificationViolationUser?.identityRoomMember?.userId).isEqualTo(carolRoomMember.userId)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange handles room becoming encrypted scenario`() = runTest {
|
||||
val identityStateChangesFlow = MutableStateFlow(
|
||||
listOf(IdentityStateChange(bobRoomMember.userId, IdentityState.Pinned))
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = false))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = true).test {
|
||||
// Should not emit anything initially as room is not encrypted
|
||||
expectNoEvents()
|
||||
|
||||
// Room becomes encrypted
|
||||
joinedRoom.baseRoom.givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
|
||||
val result = awaitItem()
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result.first().identityRoomMember.userId).isEqualTo(bobRoomMember.userId)
|
||||
assertThat(result.first().identityState).isEqualTo(IdentityState.Pinned)
|
||||
|
||||
// Add more identity changes after encryption is enabled
|
||||
identityStateChangesFlow.value = listOf(
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.Pinned),
|
||||
IdentityStateChange(aliceRoomMember.userId, IdentityState.VerificationViolation)
|
||||
)
|
||||
|
||||
val updatedResult = awaitItem()
|
||||
assertThat(updatedResult).hasSize(2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `roomMemberIdentityStateChange does not emit duplicates for same state`() = runTest {
|
||||
val identityStateChangesFlow = MutableSharedFlow<List<IdentityStateChange>>()
|
||||
val identityStateChanges = listOf(
|
||||
IdentityStateChange(bobRoomMember.userId, IdentityState.Verified)
|
||||
)
|
||||
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
identityStateChangesFlow = identityStateChangesFlow,
|
||||
baseRoom = FakeJoinedRoom().baseRoom.apply {
|
||||
givenRoomInfo(aRoomInfo(isEncrypted = true))
|
||||
givenRoomMembersState(
|
||||
RoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
aliceRoomMember,
|
||||
bobRoomMember
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
joinedRoom.roomMemberIdentityStateChange(waitForEncryption = false).test {
|
||||
identityStateChangesFlow.emit(identityStateChanges)
|
||||
|
||||
val firstResult = awaitItem()
|
||||
assertThat(firstResult).hasSize(1)
|
||||
|
||||
// Emit the same state again
|
||||
identityStateChangesFlow.emit(identityStateChanges)
|
||||
|
||||
// Should not emit a new item due to distinctUntilChanged
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user