use std::collections::BTreeSet;
use ruma::{
RoomId,
events::{
AnySyncStateEvent, SyncStateEvent,
room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
},
serde::Raw,
};
use serde::Deserialize;
use tracing::warn;
use super::Context;
use crate::store::BaseStateStore;
pub mod sync {
use std::{collections::BTreeSet, iter};
use ruma::{
OwnedUserId, RoomId, UserId,
events::{
AnySyncTimelineEvent, SyncStateEvent,
room::member::{MembershipState, RoomMemberEventContent},
},
};
use tracing::{error, instrument};
use super::{super::profiles, AnySyncStateEvent, Context, Raw};
#[cfg(feature = "experimental-encrypted-state-events")]
use crate::response_processors::e2ee;
use crate::{
RoomInfo,
store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
sync::State,
};
impl State {
pub(crate) fn collect(
&self,
timeline: &[Raw<AnySyncTimelineEvent>],
) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
match self {
Self::Before(events) => {
super::collect(events.iter().chain(timeline.iter().filter_map(|raw_event| {
match raw_event.get_field::<&str>("state_key") {
Ok(Some(_)) => Some(raw_event.cast_ref_unchecked()),
_ => None,
}
})))
}
Self::After(events) => super::collect(events),
}
}
}
#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
pub async fn dispatch<U>(
context: &mut Context,
(raw_events, events): (&[Raw<AnySyncStateEvent>], &[AnySyncStateEvent]),
room_info: &mut RoomInfo,
ambiguity_cache: &mut AmbiguityCache,
new_users: &mut U,
state_store: &BaseStateStore,
#[cfg(feature = "experimental-encrypted-state-events")] e2ee: e2ee::E2EE<'_>,
) -> StoreResult<()>
where
U: NewUsers,
{
for (raw_event, event) in iter::zip(raw_events, events) {
match event {
AnySyncStateEvent::RoomMember(member) => {
room_info.handle_state_event(event);
dispatch_room_member(
context,
&room_info.room_id,
member,
ambiguity_cache,
new_users,
)
.await?;
}
AnySyncStateEvent::RoomCreate(create) => {
let edited_create = super::validate_create_event_predecessor(
context,
room_info.room_id(),
create,
state_store,
);
room_info.handle_state_event(
edited_create.map(Into::into).as_ref().unwrap_or(event),
);
}
AnySyncStateEvent::RoomTombstone(tombstone) => {
if super::is_tombstone_event_valid(
context,
room_info.room_id(),
tombstone,
state_store,
) {
room_info.handle_state_event(event);
} else {
error!(
room_id = ?room_info.room_id(),
?tombstone,
"`m.room.tombstone` event is invalid, it creates a loop"
);
continue;
}
}
#[cfg(feature = "experimental-encrypted-state-events")]
AnySyncStateEvent::RoomEncrypted(SyncStateEvent::Original(outer)) => {
use matrix_sdk_crypto::RoomEventDecryptionResult;
use tracing::{trace, warn};
trace!(event_id = ?outer.event_id, "Received encrypted state event, attempting decryption...");
let Some(olm_machine) = e2ee.olm_machine else {
continue;
};
let decrypted_event = olm_machine
.try_decrypt_room_event(
raw_event.cast_ref_unchecked(),
&room_info.room_id,
e2ee.decryption_settings,
)
.await
.expect("OlmMachine was not started");
let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event
else {
warn!(event_id = ?outer.event_id, "Failed to decrypt state event");
continue;
};
let deserialized_event = match decrypted_event
.event
.deserialize_as::<AnySyncTimelineEvent>()
{
Ok(event) => event,
Err(err) => {
warn!(event_id = ?outer.event_id, "Failed to decrypt state event: {err}");
continue;
}
};
let AnySyncTimelineEvent::State(event) = deserialized_event else {
continue;
};
trace!(event_id = ?outer.event_id, "Decrypted state event successfully.");
room_info.handle_state_event(&event);
}
_ => {
room_info.handle_state_event(event);
}
}
context
.state_changes
.state
.entry(room_info.room_id.to_owned())
.or_default()
.entry(event.event_type())
.or_default()
.insert(event.state_key().to_owned(), raw_event.clone());
}
Ok(())
}
async fn dispatch_room_member<U>(
context: &mut Context,
room_id: &RoomId,
event: &SyncStateEvent<RoomMemberEventContent>,
ambiguity_cache: &mut AmbiguityCache,
new_users: &mut U,
) -> StoreResult<()>
where
U: NewUsers,
{
ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
match event.membership() {
MembershipState::Join | MembershipState::Invite => {
new_users.insert(event.state_key());
}
_ => (),
}
profiles::upsert_or_delete(context, room_id, event);
Ok(())
}
pub(crate) trait NewUsers {
fn insert(&mut self, user_id: &UserId);
}
impl NewUsers for BTreeSet<OwnedUserId> {
fn insert(&mut self, user_id: &UserId) {
self.insert(user_id.to_owned());
}
}
impl NewUsers for () {
fn insert(&mut self, _user_id: &UserId) {}
}
}
pub mod stripped {
use std::{collections::BTreeMap, iter};
use ruma::{events::AnyStrippedStateEvent, push::Action};
use tracing::instrument;
use super::{
super::{notification, timeline},
Context, Raw,
};
use crate::{Result, Room, RoomInfo};
pub fn collect(
raw_events: &[Raw<AnyStrippedStateEvent>],
) -> (Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>) {
super::collect(raw_events)
}
#[instrument(skip_all, fields(room_id = ?room_info.room_id))]
pub(crate) async fn dispatch_invite_or_knock(
context: &mut Context,
(raw_events, events): (&[Raw<AnyStrippedStateEvent>], &[AnyStrippedStateEvent]),
room: &Room,
room_info: &mut RoomInfo,
mut notification: notification::Notification<'_>,
) -> Result<()> {
let mut state_events = BTreeMap::new();
for (raw_event, event) in iter::zip(raw_events, events) {
room_info.handle_stripped_state_event(event);
state_events
.entry(event.event_type())
.or_insert_with(BTreeMap::new)
.insert(event.state_key().to_owned(), raw_event.clone());
}
context
.state_changes
.stripped_state
.insert(room_info.room_id().to_owned(), state_events.clone());
if let Some(push_condition_room_ctx) =
timeline::get_push_room_context(context, room, room_info).await?
{
for event in state_events.values().flat_map(|map| map.values()) {
notification
.push_notification_from_event_if(
&push_condition_room_ctx,
event,
Action::should_notify,
)
.await;
}
}
Ok(())
}
}
fn collect<'a, I, T>(raw_events: I) -> (Vec<Raw<T>>, Vec<T>)
where
I: IntoIterator<Item = &'a Raw<T>>,
T: Deserialize<'a> + 'a,
{
raw_events
.into_iter()
.filter_map(|raw_event| match raw_event.deserialize() {
Ok(event) => Some((raw_event.clone(), event)),
Err(e) => {
warn!("Couldn't deserialize stripped state event: {e}");
None
}
})
.unzip()
}
pub fn validate_create_event_predecessor(
context: &mut Context,
room_id: &RoomId,
event: &SyncStateEvent<RoomCreateEventContent>,
state_store: &BaseStateStore,
) -> Option<SyncStateEvent<RoomCreateEventContent>> {
let mut already_seen = BTreeSet::new();
already_seen.insert(room_id.to_owned());
let content = match event {
SyncStateEvent::Original(event) => &event.content,
SyncStateEvent::Redacted(event) => &event.content,
};
let Some(mut predecessor_room_id) =
content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
else {
return None;
};
loop {
if already_seen.contains(&predecessor_room_id) {
let mut event = event.clone();
match &mut event {
SyncStateEvent::Original(event) => event.content.predecessor.take(),
SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
};
return Some(event);
}
already_seen.insert(predecessor_room_id.clone());
let Some(next_predecessor_room_id) = context
.state_changes
.room_infos
.get(&predecessor_room_id)
.and_then(|room_info| Some(room_info.create()?.predecessor.as_ref()?.room_id.clone()))
.or_else(|| {
state_store
.room(&predecessor_room_id)
.and_then(|room| Some(room.predecessor_room()?.room_id))
})
else {
break;
};
predecessor_room_id = next_predecessor_room_id;
}
None
}
pub fn is_tombstone_event_valid(
context: &mut Context,
room_id: &RoomId,
event: &SyncStateEvent<RoomTombstoneEventContent>,
state_store: &BaseStateStore,
) -> bool {
let mut already_seen = BTreeSet::new();
already_seen.insert(room_id.to_owned());
let Some(mut successor_room_id) =
event.as_original().map(|event| event.content.replacement_room.clone())
else {
return true;
};
loop {
if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
return false;
}
already_seen.insert(successor_room_id.clone());
let Some(next_successor_room_id) = context
.state_changes
.room_infos
.get(&successor_room_id)
.and_then(|room_info| Some(room_info.tombstone()?.replacement_room.clone()))
.or_else(|| {
state_store
.room(&successor_room_id)
.and_then(|room| Some(room.successor_room()?.room_id))
})
else {
break;
};
successor_room_id = next_successor_room_id;
}
true
}
#[cfg(test)]
mod tests {
use assert_matches2::assert_matches;
use matrix_sdk_test::{
DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, TestResult,
async_test, event_factory::EventFactory,
};
use ruma::{RoomVersionId, event_id, room_id, user_id};
use crate::test_utils::logged_in_base_client;
#[async_test]
async fn test_not_possible_to_overwrite_m_room_create() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let room_id_2 = room_id!("!r2");
let client = logged_in_base_client(None).await;
{
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0)
.add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("42")?),
)
.add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("43")?),
),
)
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("44")?),
))
.add_joined_room(JoinedRoomBuilder::new(room_id_2))
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
assert_eq!(
client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
"42"
);
assert_eq!(
client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
"44"
);
assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
}
{
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("45")?),
))
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("46")?),
))
.add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("47")?),
))
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
assert_eq!(
client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
"42"
);
assert_eq!(
client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
"44"
);
assert_eq!(
client.get_room(room_id_2).unwrap().create_content().unwrap().room_version.as_str(),
"47"
);
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_no_newly_tombstoned_rooms() {
let client = logged_in_base_client(None).await;
{
let response = SyncResponseBuilder::new()
.add_joined_room(JoinedRoomBuilder::new(room_id!("!r0")))
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
}
}
#[async_test]
async fn test_check_room_upgrades_no_error() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let room_id_2 = room_id!("!r2");
let client = logged_in_base_client(None).await;
{
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("41")?),
))
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none());
assert!(room_0.successor_room().is_none());
}
{
let tombstone_event_id = event_id!("$ev0");
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
))
.add_joined_room(
JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_0),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert_eq!(
room_0.successor_room().expect("room 0 must have a successor").room_id,
room_id_1,
"room 0 does not have the expected successor",
);
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
}
{
let tombstone_event_id = event_id!("$ev1");
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
))
.add_joined_room(
JoinedRoomBuilder::new(room_id_2).add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("43")?)
.predecessor(room_id_1),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert_eq!(
room_1.successor_room().expect("room 1 must have a successor").room_id,
room_id_2,
"room 1 does not have the expected successor",
);
let room_2 = client.get_room(room_id_2).unwrap();
assert_eq!(
room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
room_id_1,
"room 2 does not have the expected predecessor",
);
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_no_loop_within_misordered_rooms() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r1");
let room_id_1 = room_id!("!r0");
let room_id_2 = room_id!("!r2");
let client = logged_in_base_client(None).await;
{
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0)
.add_timeline_event(
event_factory.create(sender, RoomVersionId::try_from("41")?),
)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_1)
.event_id(event_id!("$ev0")),
),
)
.add_joined_room(
JoinedRoomBuilder::new(room_id_1)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_0),
)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_2)
.event_id(event_id!("$ev1")),
),
)
.add_joined_room(
JoinedRoomBuilder::new(room_id_2).add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("43")?)
.predecessor(room_id_1),
),
)
.build_sync_response();
{
let mut rooms = response.rooms.join.keys();
assert_eq!(rooms.next().unwrap(), room_id_1);
assert_eq!(rooms.next().unwrap(), room_id_0);
assert_eq!(rooms.next().unwrap(), room_id_2);
assert!(rooms.next().is_none());
}
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert_eq!(
room_0.successor_room().expect("room 0 must have a successor").room_id,
room_id_1,
"room 0 does not have the expected successor",
);
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert_eq!(
room_1.successor_room().expect("room 1 must have a successor").room_id,
room_id_2,
"room 1 does not have the expected successor",
);
let room_2 = client.get_room(room_id_2).unwrap();
assert_eq!(
room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
room_id_1,
"room 2 does not have the expected predecessor",
);
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_shortest_invalid_successor() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let client = logged_in_base_client(None).await;
{
let tombstone_event_id = event_id!("$ev0");
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory
.room_tombstone("hello", room_id_0)
.event_id(tombstone_event_id),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_invalid_successor() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let room_id_2 = room_id!("!r2");
let client = logged_in_base_client(None).await;
{
let tombstone_event_id = event_id!("$ev0");
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
))
.add_joined_room(
JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_0),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert_eq!(
room_0.successor_room().expect("room 0 must have a successor").room_id,
room_id_1,
"room 0 does not have the expected successor",
);
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
}
{
let tombstone_event_id = event_id!("$ev1");
let response = response_builder
.add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
))
.add_joined_room(
JoinedRoomBuilder::new(room_id_2)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("43")?)
.predecessor(room_id_1),
)
.add_timeline_event(
event_factory
.room_tombstone("hehe", room_id_0)
.event_id(event_id!("$ev_foo")),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert_eq!(
room_0.successor_room().expect("room 0 must have a successor").room_id,
room_id_1,
"room 0 does not have the expected successor",
);
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert_eq!(
room_1.successor_room().expect("room 1 must have a successor").room_id,
room_id_2,
"room 1 does not have the expected successor",
);
let room_2 = client.get_room(room_id_2).unwrap();
assert_eq!(
room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
room_id_1,
"room 2 does not have the expected predecessor",
);
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_shortest_invalid_predecessor() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let client = logged_in_base_client(None).await;
{
let tombstone_event_id = event_id!("$ev0");
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0).add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_0)
.event_id(tombstone_event_id),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_shortest_loop() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let client = logged_in_base_client(None).await;
{
let tombstone_event_id = event_id!("$ev0");
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_0)
.event_id(tombstone_event_id),
)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_0),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
}
Ok(())
}
#[async_test]
async fn test_check_room_upgrades_loop() -> TestResult {
let sender = user_id!("@mnt_io:matrix.org");
let event_factory = EventFactory::new().sender(sender);
let mut response_builder = SyncResponseBuilder::new();
let room_id_0 = room_id!("!r0");
let room_id_1 = room_id!("!r1");
let room_id_2 = room_id!("!r2");
let client = logged_in_base_client(None).await;
{
let response = response_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id_0)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("42")?)
.predecessor(room_id_2),
)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_1)
.event_id(event_id!("$ev0")),
),
)
.add_joined_room(
JoinedRoomBuilder::new(room_id_1)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("43")?)
.predecessor(room_id_0),
)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_2)
.event_id(event_id!("$ev1")),
),
)
.add_joined_room(
JoinedRoomBuilder::new(room_id_2)
.add_timeline_event(
event_factory
.create(sender, RoomVersionId::try_from("44")?)
.predecessor(room_id_1),
)
.add_timeline_event(
event_factory
.room_tombstone("hello", room_id_0)
.event_id(event_id!("$ev2")),
),
)
.build_sync_response();
assert!(client.receive_sync_response(response).await.is_ok());
let room_0 = client.get_room(room_id_0).unwrap();
assert_eq!(
room_0.predecessor_room().expect("room 0 must have a predecessor").room_id,
room_id_2,
"room 0 does not have the expected predecessor"
);
assert_eq!(
room_0.successor_room().expect("room 0 must have a successor").room_id,
room_id_1,
"room 0 does not have the expected successor",
);
let room_1 = client.get_room(room_id_1).unwrap();
assert_eq!(
room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
room_id_0,
"room 1 does not have the expected predecessor",
);
assert_eq!(
room_1.successor_room().expect("room 1 must have a successor").room_id,
room_id_2,
"room 1 does not have the expected successor",
);
let room_2 = client.get_room(room_id_2).unwrap();
assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
}
Ok(())
}
#[async_test]
async fn test_state_events_after_sync() -> TestResult {
let user_id = user_id!("@u:u.to");
let client = logged_in_base_client(Some(user_id)).await;
let mut sync_builder = SyncResponseBuilder::new();
let room_name = EventFactory::new()
.sender(user_id)
.room_topic("this is the test topic in the timeline")
.event_id(event_id!("$2"))
.into_raw_sync();
let response = sync_builder
.add_joined_room(
JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
.add_timeline_event(room_name)
.add_state_event(StateTestEvent::Create)
.add_state_event(StateTestEvent::PowerLevels),
)
.build_sync_response();
client.receive_sync_response(response).await?;
let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
assert!(room.power_levels().await.is_ok());
assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
Ok(())
}
}