1use std::collections::BTreeSet;
16
17use ruma::{
18 RoomId,
19 events::{
20 AnySyncStateEvent, SyncStateEvent,
21 room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
22 },
23 serde::Raw,
24};
25use serde::Deserialize;
26use tracing::warn;
27
28use super::Context;
29use crate::store::BaseStateStore;
30
31pub mod sync {
33 use std::{collections::BTreeSet, iter};
34
35 use ruma::{
36 OwnedUserId, RoomId, UserId,
37 events::{
38 AnySyncTimelineEvent, SyncStateEvent,
39 room::member::{MembershipState, RoomMemberEventContent},
40 },
41 };
42 use tracing::{error, instrument};
43
44 use super::{super::profiles, AnySyncStateEvent, Context, Raw};
45 #[cfg(feature = "experimental-encrypted-state-events")]
46 use crate::response_processors::e2ee;
47 use crate::{
48 RoomInfo,
49 store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
50 sync::State,
51 };
52
53 impl State {
54 pub(crate) fn collect(
59 &self,
60 timeline: &[Raw<AnySyncTimelineEvent>],
61 ) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
62 match self {
63 Self::Before(events) => {
64 super::collect(events.iter().chain(timeline.iter().filter_map(|raw_event| {
65 match raw_event.get_field::<&str>("state_key") {
67 Ok(Some(_)) => Some(raw_event.cast_ref_unchecked()),
68 _ => None,
69 }
70 })))
71 }
72 Self::After(events) => super::collect(events),
73 }
74 }
75 }
76
77 #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
87 pub async fn dispatch<U>(
88 context: &mut Context,
89 (raw_events, events): (&[Raw<AnySyncStateEvent>], &[AnySyncStateEvent]),
90 room_info: &mut RoomInfo,
91 ambiguity_cache: &mut AmbiguityCache,
92 new_users: &mut U,
93 state_store: &BaseStateStore,
94 #[cfg(feature = "experimental-encrypted-state-events")] e2ee: e2ee::E2EE<'_>,
95 ) -> StoreResult<()>
96 where
97 U: NewUsers,
98 {
99 for (raw_event, event) in iter::zip(raw_events, events) {
100 match event {
101 AnySyncStateEvent::RoomMember(member) => {
102 room_info.handle_state_event(event);
103
104 dispatch_room_member(
105 context,
106 &room_info.room_id,
107 member,
108 ambiguity_cache,
109 new_users,
110 )
111 .await?;
112 }
113
114 AnySyncStateEvent::RoomCreate(create) => {
115 let edited_create = super::validate_create_event_predecessor(
116 context,
117 room_info.room_id(),
118 create,
119 state_store,
120 );
121
122 room_info.handle_state_event(
123 edited_create.map(Into::into).as_ref().unwrap_or(event),
124 );
125 }
126
127 AnySyncStateEvent::RoomTombstone(tombstone) => {
128 if super::is_tombstone_event_valid(
129 context,
130 room_info.room_id(),
131 tombstone,
132 state_store,
133 ) {
134 room_info.handle_state_event(event);
135 } else {
136 error!(
137 room_id = ?room_info.room_id(),
138 ?tombstone,
139 "`m.room.tombstone` event is invalid, it creates a loop"
140 );
141
142 continue;
145 }
146 }
147
148 #[cfg(feature = "experimental-encrypted-state-events")]
149 AnySyncStateEvent::RoomEncrypted(SyncStateEvent::Original(outer)) => {
150 use matrix_sdk_crypto::RoomEventDecryptionResult;
151 use tracing::{trace, warn};
152
153 trace!(event_id = ?outer.event_id, "Received encrypted state event, attempting decryption...");
154
155 let Some(olm_machine) = e2ee.olm_machine else {
156 continue;
157 };
158
159 let decrypted_event = olm_machine
160 .try_decrypt_room_event(
161 raw_event.cast_ref_unchecked(),
162 &room_info.room_id,
163 e2ee.decryption_settings,
164 )
165 .await
166 .expect("OlmMachine was not started");
167
168 let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event
170 else {
171 warn!(event_id = ?outer.event_id, "Failed to decrypt state event");
172 continue;
173 };
174
175 let deserialized_event = match decrypted_event
178 .event
179 .deserialize_as::<AnySyncTimelineEvent>()
180 {
181 Ok(event) => event,
182 Err(err) => {
183 warn!(event_id = ?outer.event_id, "Failed to decrypt state event: {err}");
184 continue;
185 }
186 };
187
188 let AnySyncTimelineEvent::State(event) = deserialized_event else {
190 continue;
191 };
192
193 trace!(event_id = ?outer.event_id, "Decrypted state event successfully.");
194 room_info.handle_state_event(&event);
195 }
196
197 _ => {
198 room_info.handle_state_event(event);
199 }
200 }
201
202 context
203 .state_changes
204 .state
205 .entry(room_info.room_id.to_owned())
206 .or_default()
207 .entry(event.event_type())
208 .or_default()
209 .insert(event.state_key().to_owned(), raw_event.clone());
210 }
211
212 Ok(())
213 }
214
215 async fn dispatch_room_member<U>(
217 context: &mut Context,
218 room_id: &RoomId,
219 event: &SyncStateEvent<RoomMemberEventContent>,
220 ambiguity_cache: &mut AmbiguityCache,
221 new_users: &mut U,
222 ) -> StoreResult<()>
223 where
224 U: NewUsers,
225 {
226 ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
227
228 match event.membership() {
229 MembershipState::Join | MembershipState::Invite => {
230 new_users.insert(event.state_key());
231 }
232 _ => (),
233 }
234
235 profiles::upsert_or_delete(context, room_id, event);
236
237 Ok(())
238 }
239
240 pub(crate) trait NewUsers {
242 fn insert(&mut self, user_id: &UserId);
244 }
245
246 impl NewUsers for BTreeSet<OwnedUserId> {
247 fn insert(&mut self, user_id: &UserId) {
248 self.insert(user_id.to_owned());
249 }
250 }
251
252 impl NewUsers for () {
253 fn insert(&mut self, _user_id: &UserId) {}
254 }
255}
256
257pub mod stripped {
259 use std::{collections::BTreeMap, iter};
260
261 use ruma::{events::AnyStrippedStateEvent, push::Action};
262 use tracing::instrument;
263
264 use super::{
265 super::{notification, timeline},
266 Context, Raw,
267 };
268 use crate::{Result, Room, RoomInfo};
269
270 pub fn collect(
272 raw_events: &[Raw<AnyStrippedStateEvent>],
273 ) -> (Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>) {
274 super::collect(raw_events)
275 }
276
277 #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
294 pub(crate) async fn dispatch_invite_or_knock(
295 context: &mut Context,
296 (raw_events, events): (&[Raw<AnyStrippedStateEvent>], &[AnyStrippedStateEvent]),
297 room: &Room,
298 room_info: &mut RoomInfo,
299 mut notification: notification::Notification<'_>,
300 ) -> Result<()> {
301 let mut state_events = BTreeMap::new();
302
303 for (raw_event, event) in iter::zip(raw_events, events) {
304 room_info.handle_stripped_state_event(event);
305 state_events
306 .entry(event.event_type())
307 .or_insert_with(BTreeMap::new)
308 .insert(event.state_key().to_owned(), raw_event.clone());
309 }
310
311 context
312 .state_changes
313 .stripped_state
314 .insert(room_info.room_id().to_owned(), state_events.clone());
315
316 if let Some(push_condition_room_ctx) =
319 timeline::get_push_room_context(context, room, room_info).await?
320 {
321 for event in state_events.values().flat_map(|map| map.values()) {
323 notification
324 .push_notification_from_event_if(
325 &push_condition_room_ctx,
326 event,
327 Action::should_notify,
328 )
329 .await;
330 }
331 }
332
333 Ok(())
334 }
335}
336
337fn collect<'a, I, T>(raw_events: I) -> (Vec<Raw<T>>, Vec<T>)
338where
339 I: IntoIterator<Item = &'a Raw<T>>,
340 T: Deserialize<'a> + 'a,
341{
342 raw_events
343 .into_iter()
344 .filter_map(|raw_event| match raw_event.deserialize() {
345 Ok(event) => Some((raw_event.clone(), event)),
346 Err(e) => {
347 warn!("Couldn't deserialize stripped state event: {e}");
348 None
349 }
350 })
351 .unzip()
352}
353
354pub fn validate_create_event_predecessor(
359 context: &mut Context,
360 room_id: &RoomId,
361 event: &SyncStateEvent<RoomCreateEventContent>,
362 state_store: &BaseStateStore,
363) -> Option<SyncStateEvent<RoomCreateEventContent>> {
364 let mut already_seen = BTreeSet::new();
365 already_seen.insert(room_id.to_owned());
366
367 let content = match event {
369 SyncStateEvent::Original(event) => &event.content,
370 SyncStateEvent::Redacted(event) => &event.content,
371 };
372
373 let Some(mut predecessor_room_id) =
374 content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
375 else {
376 return None;
378 };
379
380 loop {
381 if already_seen.contains(&predecessor_room_id) {
385 let mut event = event.clone();
388
389 match &mut event {
390 SyncStateEvent::Original(event) => event.content.predecessor.take(),
391 SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
392 };
393
394 return Some(event);
395 }
396
397 already_seen.insert(predecessor_room_id.clone());
398
399 let Some(next_predecessor_room_id) = context
402 .state_changes
403 .room_infos
404 .get(&predecessor_room_id)
405 .and_then(|room_info| Some(room_info.create()?.predecessor.as_ref()?.room_id.clone()))
406 .or_else(|| {
407 state_store
408 .room(&predecessor_room_id)
409 .and_then(|room| Some(room.predecessor_room()?.room_id))
410 })
411 else {
412 break;
414 };
415
416 predecessor_room_id = next_predecessor_room_id;
417 }
418
419 None
420}
421
422pub fn is_tombstone_event_valid(
424 context: &mut Context,
425 room_id: &RoomId,
426 event: &SyncStateEvent<RoomTombstoneEventContent>,
427 state_store: &BaseStateStore,
428) -> bool {
429 let mut already_seen = BTreeSet::new();
430 already_seen.insert(room_id.to_owned());
431
432 let Some(mut successor_room_id) =
433 event.as_original().map(|event| event.content.replacement_room.clone())
434 else {
435 return true;
437 };
438
439 loop {
440 if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
443 return false;
445 }
446
447 already_seen.insert(successor_room_id.clone());
448
449 let Some(next_successor_room_id) = context
451 .state_changes
452 .room_infos
453 .get(&successor_room_id)
454 .and_then(|room_info| Some(room_info.tombstone()?.replacement_room.clone()))
455 .or_else(|| {
456 state_store
457 .room(&successor_room_id)
458 .and_then(|room| Some(room.successor_room()?.room_id))
459 })
460 else {
461 break;
463 };
464
465 successor_room_id = next_successor_room_id;
466 }
467
468 true
469}
470
471#[cfg(test)]
472mod tests {
473 use assert_matches2::assert_matches;
474 use matrix_sdk_test::{
475 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, TestResult,
476 async_test, event_factory::EventFactory,
477 };
478 use ruma::{RoomVersionId, event_id, room_id, user_id};
479
480 use crate::test_utils::logged_in_base_client;
481
482 #[async_test]
483 async fn test_not_possible_to_overwrite_m_room_create() -> TestResult {
484 let sender = user_id!("@mnt_io:matrix.org");
485 let event_factory = EventFactory::new().sender(sender);
486 let mut response_builder = SyncResponseBuilder::new();
487 let room_id_0 = room_id!("!r0");
488 let room_id_1 = room_id!("!r1");
489 let room_id_2 = room_id!("!r2");
490
491 let client = logged_in_base_client(None).await;
492
493 {
497 let response = response_builder
498 .add_joined_room(
499 JoinedRoomBuilder::new(room_id_0)
500 .add_timeline_event(
501 event_factory.create(sender, RoomVersionId::try_from("42")?),
502 )
503 .add_timeline_event(
504 event_factory.create(sender, RoomVersionId::try_from("43")?),
505 ),
506 )
507 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
508 event_factory.create(sender, RoomVersionId::try_from("44")?),
509 ))
510 .add_joined_room(JoinedRoomBuilder::new(room_id_2))
511 .build_sync_response();
512
513 assert!(client.receive_sync_response(response).await.is_ok());
514
515 assert_eq!(
518 client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
519 "42"
520 );
521 assert_eq!(
523 client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
524 "44"
525 );
526 assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
528 }
529
530 {
534 let response = response_builder
535 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
536 event_factory.create(sender, RoomVersionId::try_from("45")?),
537 ))
538 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
539 event_factory.create(sender, RoomVersionId::try_from("46")?),
540 ))
541 .add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
542 event_factory.create(sender, RoomVersionId::try_from("47")?),
543 ))
544 .build_sync_response();
545
546 assert!(client.receive_sync_response(response).await.is_ok());
547
548 assert_eq!(
551 client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
552 "42"
553 );
554 assert_eq!(
557 client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
558 "44"
559 );
560 assert_eq!(
562 client.get_room(room_id_2).unwrap().create_content().unwrap().room_version.as_str(),
563 "47"
564 );
565 }
566
567 Ok(())
568 }
569
570 #[async_test]
571 async fn test_check_room_upgrades_no_newly_tombstoned_rooms() {
572 let client = logged_in_base_client(None).await;
573
574 {
576 let response = SyncResponseBuilder::new()
577 .add_joined_room(JoinedRoomBuilder::new(room_id!("!r0")))
578 .build_sync_response();
579
580 assert!(client.receive_sync_response(response).await.is_ok());
581 }
582 }
583
584 #[async_test]
585 async fn test_check_room_upgrades_no_error() -> TestResult {
586 let sender = user_id!("@mnt_io:matrix.org");
587 let event_factory = EventFactory::new().sender(sender);
588 let mut response_builder = SyncResponseBuilder::new();
589 let room_id_0 = room_id!("!r0");
590 let room_id_1 = room_id!("!r1");
591 let room_id_2 = room_id!("!r2");
592
593 let client = logged_in_base_client(None).await;
594
595 {
597 let response = response_builder
598 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
599 event_factory.create(sender, RoomVersionId::try_from("41")?),
601 ))
602 .build_sync_response();
603
604 assert!(client.receive_sync_response(response).await.is_ok());
605
606 let room_0 = client.get_room(room_id_0).unwrap();
607
608 assert!(room_0.predecessor_room().is_none());
609 assert!(room_0.successor_room().is_none());
610 }
611
612 {
614 let tombstone_event_id = event_id!("$ev0");
615 let response = response_builder
616 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
617 event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
619 ))
620 .add_joined_room(
621 JoinedRoomBuilder::new(room_id_1).add_timeline_event(
622 event_factory
624 .create(sender, RoomVersionId::try_from("42")?)
625 .predecessor(room_id_0),
626 ),
627 )
628 .build_sync_response();
629
630 assert!(client.receive_sync_response(response).await.is_ok());
631
632 let room_0 = client.get_room(room_id_0).unwrap();
633
634 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
635 assert_eq!(
636 room_0.successor_room().expect("room 0 must have a successor").room_id,
637 room_id_1,
638 "room 0 does not have the expected successor",
639 );
640
641 let room_1 = client.get_room(room_id_1).unwrap();
642
643 assert_eq!(
644 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
645 room_id_0,
646 "room 1 does not have the expected predecessor",
647 );
648 assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
649 }
650
651 {
653 let tombstone_event_id = event_id!("$ev1");
654 let response = response_builder
655 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
656 event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
658 ))
659 .add_joined_room(
660 JoinedRoomBuilder::new(room_id_2).add_timeline_event(
661 event_factory
663 .create(sender, RoomVersionId::try_from("43")?)
664 .predecessor(room_id_1),
665 ),
666 )
667 .build_sync_response();
668
669 assert!(client.receive_sync_response(response).await.is_ok());
670
671 let room_1 = client.get_room(room_id_1).unwrap();
672
673 assert_eq!(
674 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
675 room_id_0,
676 "room 1 does not have the expected predecessor",
677 );
678 assert_eq!(
679 room_1.successor_room().expect("room 1 must have a successor").room_id,
680 room_id_2,
681 "room 1 does not have the expected successor",
682 );
683
684 let room_2 = client.get_room(room_id_2).unwrap();
685
686 assert_eq!(
687 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
688 room_id_1,
689 "room 2 does not have the expected predecessor",
690 );
691 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
692 }
693
694 Ok(())
695 }
696
697 #[async_test]
698 async fn test_check_room_upgrades_no_loop_within_misordered_rooms() -> TestResult {
699 let sender = user_id!("@mnt_io:matrix.org");
700 let event_factory = EventFactory::new().sender(sender);
701 let mut response_builder = SyncResponseBuilder::new();
702 let room_id_0 = room_id!("!r1");
705 let room_id_1 = room_id!("!r0");
706 let room_id_2 = room_id!("!r2");
707
708 let client = logged_in_base_client(None).await;
709
710 {
713 let response = response_builder
714 .add_joined_room(
716 JoinedRoomBuilder::new(room_id_0)
717 .add_timeline_event(
718 event_factory.create(sender, RoomVersionId::try_from("41")?),
720 )
721 .add_timeline_event(
722 event_factory
724 .room_tombstone("hello", room_id_1)
725 .event_id(event_id!("$ev0")),
726 ),
727 )
728 .add_joined_room(
730 JoinedRoomBuilder::new(room_id_1)
731 .add_timeline_event(
732 event_factory
734 .create(sender, RoomVersionId::try_from("42")?)
735 .predecessor(room_id_0),
736 )
737 .add_timeline_event(
738 event_factory
740 .room_tombstone("hello", room_id_2)
741 .event_id(event_id!("$ev1")),
742 ),
743 )
744 .add_joined_room(
746 JoinedRoomBuilder::new(room_id_2).add_timeline_event(
747 event_factory
749 .create(sender, RoomVersionId::try_from("43")?)
750 .predecessor(room_id_1),
751 ),
752 )
753 .build_sync_response();
754
755 {
757 let mut rooms = response.rooms.join.keys();
758
759 assert_eq!(rooms.next().unwrap(), room_id_1);
761 assert_eq!(rooms.next().unwrap(), room_id_0);
762 assert_eq!(rooms.next().unwrap(), room_id_2);
763 assert!(rooms.next().is_none());
764 }
765
766 assert!(client.receive_sync_response(response).await.is_ok());
768
769 let room_0 = client.get_room(room_id_0).unwrap();
770
771 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
772 assert_eq!(
773 room_0.successor_room().expect("room 0 must have a successor").room_id,
774 room_id_1,
775 "room 0 does not have the expected successor",
776 );
777
778 let room_1 = client.get_room(room_id_1).unwrap();
779
780 assert_eq!(
781 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
782 room_id_0,
783 "room 1 does not have the expected predecessor",
784 );
785 assert_eq!(
786 room_1.successor_room().expect("room 1 must have a successor").room_id,
787 room_id_2,
788 "room 1 does not have the expected successor",
789 );
790
791 let room_2 = client.get_room(room_id_2).unwrap();
792
793 assert_eq!(
794 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
795 room_id_1,
796 "room 2 does not have the expected predecessor",
797 );
798 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
799 }
800
801 Ok(())
802 }
803
804 #[async_test]
805 async fn test_check_room_upgrades_shortest_invalid_successor() -> TestResult {
806 let sender = user_id!("@mnt_io:matrix.org");
807 let event_factory = EventFactory::new().sender(sender);
808 let mut response_builder = SyncResponseBuilder::new();
809 let room_id_0 = room_id!("!r0");
810
811 let client = logged_in_base_client(None).await;
812
813 {
815 let tombstone_event_id = event_id!("$ev0");
816 let response = response_builder
817 .add_joined_room(
818 JoinedRoomBuilder::new(room_id_0).add_timeline_event(
821 event_factory
822 .room_tombstone("hello", room_id_0)
823 .event_id(tombstone_event_id),
824 ),
825 )
826 .build_sync_response();
827
828 assert!(client.receive_sync_response(response).await.is_ok());
830
831 let room_0 = client.get_room(room_id_0).unwrap();
833
834 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
835 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
836 }
837
838 Ok(())
839 }
840
841 #[async_test]
842 async fn test_check_room_upgrades_invalid_successor() -> TestResult {
843 let sender = user_id!("@mnt_io:matrix.org");
844 let event_factory = EventFactory::new().sender(sender);
845 let mut response_builder = SyncResponseBuilder::new();
846 let room_id_0 = room_id!("!r0");
847 let room_id_1 = room_id!("!r1");
848 let room_id_2 = room_id!("!r2");
849
850 let client = logged_in_base_client(None).await;
851
852 {
854 let tombstone_event_id = event_id!("$ev0");
855 let response = response_builder
856 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
857 event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
859 ))
860 .add_joined_room(
861 JoinedRoomBuilder::new(room_id_1).add_timeline_event(
862 event_factory
864 .create(sender, RoomVersionId::try_from("42")?)
865 .predecessor(room_id_0),
866 ),
867 )
868 .build_sync_response();
869
870 assert!(client.receive_sync_response(response).await.is_ok());
871
872 let room_0 = client.get_room(room_id_0).unwrap();
873
874 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
875 assert_eq!(
876 room_0.successor_room().expect("room 0 must have a successor").room_id,
877 room_id_1,
878 "room 0 does not have the expected successor",
879 );
880
881 let room_1 = client.get_room(room_id_1).unwrap();
882
883 assert_eq!(
884 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
885 room_id_0,
886 "room 1 does not have the expected predecessor",
887 );
888 assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
889 }
890
891 {
893 let tombstone_event_id = event_id!("$ev1");
894 let response = response_builder
895 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
896 event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
898 ))
899 .add_joined_room(
900 JoinedRoomBuilder::new(room_id_2)
901 .add_timeline_event(
902 event_factory
904 .create(sender, RoomVersionId::try_from("43")?)
905 .predecessor(room_id_1),
906 )
907 .add_timeline_event(
908 event_factory
910 .room_tombstone("hehe", room_id_0)
911 .event_id(event_id!("$ev_foo")),
912 ),
913 )
914 .build_sync_response();
915
916 assert!(client.receive_sync_response(response).await.is_ok());
918
919 let room_0 = client.get_room(room_id_0).unwrap();
921
922 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
923 assert_eq!(
924 room_0.successor_room().expect("room 0 must have a successor").room_id,
925 room_id_1,
926 "room 0 does not have the expected successor",
927 );
928
929 let room_1 = client.get_room(room_id_1).unwrap();
930
931 assert_eq!(
932 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
933 room_id_0,
934 "room 1 does not have the expected predecessor",
935 );
936 assert_eq!(
937 room_1.successor_room().expect("room 1 must have a successor").room_id,
938 room_id_2,
939 "room 1 does not have the expected successor",
940 );
941
942 let room_2 = client.get_room(room_id_2).unwrap();
943
944 assert_eq!(
945 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
946 room_id_1,
947 "room 2 does not have the expected predecessor",
948 );
949 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
951 }
952
953 Ok(())
954 }
955
956 #[async_test]
957 async fn test_check_room_upgrades_shortest_invalid_predecessor() -> TestResult {
958 let sender = user_id!("@mnt_io:matrix.org");
959 let event_factory = EventFactory::new().sender(sender);
960 let mut response_builder = SyncResponseBuilder::new();
961 let room_id_0 = room_id!("!r0");
962
963 let client = logged_in_base_client(None).await;
964
965 {
967 let tombstone_event_id = event_id!("$ev0");
968 let response = response_builder
969 .add_joined_room(
970 JoinedRoomBuilder::new(room_id_0).add_timeline_event(
973 event_factory
974 .create(sender, RoomVersionId::try_from("42")?)
975 .predecessor(room_id_0)
976 .event_id(tombstone_event_id),
977 ),
978 )
979 .build_sync_response();
980
981 assert!(client.receive_sync_response(response).await.is_ok());
983
984 let room_0 = client.get_room(room_id_0).unwrap();
986
987 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
988 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
989 assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
990 }
991
992 Ok(())
993 }
994
995 #[async_test]
996 async fn test_check_room_upgrades_shortest_loop() -> TestResult {
997 let sender = user_id!("@mnt_io:matrix.org");
998 let event_factory = EventFactory::new().sender(sender);
999 let mut response_builder = SyncResponseBuilder::new();
1000 let room_id_0 = room_id!("!r0");
1001
1002 let client = logged_in_base_client(None).await;
1003
1004 {
1006 let tombstone_event_id = event_id!("$ev0");
1007 let response = response_builder
1008 .add_joined_room(
1009 JoinedRoomBuilder::new(room_id_0)
1010 .add_timeline_event(
1011 event_factory
1013 .room_tombstone("hello", room_id_0)
1014 .event_id(tombstone_event_id),
1015 )
1016 .add_timeline_event(
1017 event_factory
1019 .create(sender, RoomVersionId::try_from("42")?)
1020 .predecessor(room_id_0),
1021 ),
1022 )
1023 .build_sync_response();
1024
1025 assert!(client.receive_sync_response(response).await.is_ok());
1027
1028 let room_0 = client.get_room(room_id_0).unwrap();
1030
1031 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
1032 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
1033 assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
1034 }
1035
1036 Ok(())
1037 }
1038
1039 #[async_test]
1040 async fn test_check_room_upgrades_loop() -> TestResult {
1041 let sender = user_id!("@mnt_io:matrix.org");
1042 let event_factory = EventFactory::new().sender(sender);
1043 let mut response_builder = SyncResponseBuilder::new();
1044 let room_id_0 = room_id!("!r0");
1045 let room_id_1 = room_id!("!r1");
1046 let room_id_2 = room_id!("!r2");
1047
1048 let client = logged_in_base_client(None).await;
1049
1050 {
1056 let response = response_builder
1057 .add_joined_room(
1058 JoinedRoomBuilder::new(room_id_0)
1059 .add_timeline_event(
1060 event_factory
1062 .create(sender, RoomVersionId::try_from("42")?)
1063 .predecessor(room_id_2),
1064 )
1065 .add_timeline_event(
1066 event_factory
1068 .room_tombstone("hello", room_id_1)
1069 .event_id(event_id!("$ev0")),
1070 ),
1071 )
1072 .add_joined_room(
1073 JoinedRoomBuilder::new(room_id_1)
1074 .add_timeline_event(
1075 event_factory
1077 .create(sender, RoomVersionId::try_from("43")?)
1078 .predecessor(room_id_0),
1079 )
1080 .add_timeline_event(
1081 event_factory
1083 .room_tombstone("hello", room_id_2)
1084 .event_id(event_id!("$ev1")),
1085 ),
1086 )
1087 .add_joined_room(
1088 JoinedRoomBuilder::new(room_id_2)
1089 .add_timeline_event(
1090 event_factory
1092 .create(sender, RoomVersionId::try_from("44")?)
1093 .predecessor(room_id_1),
1094 )
1095 .add_timeline_event(
1096 event_factory
1098 .room_tombstone("hello", room_id_0)
1099 .event_id(event_id!("$ev2")),
1100 ),
1101 )
1102 .build_sync_response();
1103
1104 assert!(client.receive_sync_response(response).await.is_ok());
1106
1107 let room_0 = client.get_room(room_id_0).unwrap();
1110
1111 assert_eq!(
1112 room_0.predecessor_room().expect("room 0 must have a predecessor").room_id,
1113 room_id_2,
1114 "room 0 does not have the expected predecessor"
1115 );
1116 assert_eq!(
1117 room_0.successor_room().expect("room 0 must have a successor").room_id,
1118 room_id_1,
1119 "room 0 does not have the expected successor",
1120 );
1121
1122 let room_1 = client.get_room(room_id_1).unwrap();
1123
1124 assert_eq!(
1125 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
1126 room_id_0,
1127 "room 1 does not have the expected predecessor",
1128 );
1129 assert_eq!(
1130 room_1.successor_room().expect("room 1 must have a successor").room_id,
1131 room_id_2,
1132 "room 1 does not have the expected successor",
1133 );
1134
1135 let room_2 = client.get_room(room_id_2).unwrap();
1136
1137 assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
1139 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1140 assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
1141 }
1142
1143 Ok(())
1144 }
1145
1146 #[async_test]
1147 async fn test_state_events_after_sync() -> TestResult {
1148 let user_id = user_id!("@u:u.to");
1150
1151 let client = logged_in_base_client(Some(user_id)).await;
1152 let mut sync_builder = SyncResponseBuilder::new();
1153
1154 let room_name = EventFactory::new()
1155 .sender(user_id)
1156 .room_topic("this is the test topic in the timeline")
1157 .event_id(event_id!("$2"))
1158 .into_raw_sync();
1159
1160 let response = sync_builder
1161 .add_joined_room(
1162 JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
1163 .add_timeline_event(room_name)
1164 .add_state_event(StateTestEvent::Create)
1165 .add_state_event(StateTestEvent::PowerLevels),
1166 )
1167 .build_sync_response();
1168 client.receive_sync_response(response).await?;
1169
1170 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
1171
1172 assert!(room.power_levels().await.is_ok());
1174
1175 assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
1177
1178 Ok(())
1179 }
1180}