matrix_sdk_base/response_processors/
state_events.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::BTreeSet;
16
17use ruma::{
18    events::{
19        room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
20        AnySyncStateEvent, SyncStateEvent,
21    },
22    serde::Raw,
23    RoomId,
24};
25use serde::Deserialize;
26use tracing::warn;
27
28use super::Context;
29use crate::store::BaseStateStore;
30
31/// Collect [`AnySyncStateEvent`].
32pub mod sync {
33    use std::{collections::BTreeSet, iter};
34
35    use ruma::{
36        events::{
37            room::member::{MembershipState, RoomMemberEventContent},
38            AnySyncTimelineEvent, SyncStateEvent,
39        },
40        OwnedUserId, RoomId, UserId,
41    };
42    use tracing::{error, instrument};
43
44    use super::{super::profiles, AnySyncStateEvent, Context, Raw};
45    use crate::{
46        store::{ambiguity_map::AmbiguityCache, BaseStateStore, Result as StoreResult},
47        RoomInfo,
48    };
49
50    /// Collect [`AnySyncStateEvent`] to [`AnySyncStateEvent`].
51    pub fn collect(
52        raw_events: &[Raw<AnySyncStateEvent>],
53    ) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
54        super::collect(raw_events)
55    }
56
57    /// Collect [`AnySyncTimelineEvent`] to [`AnySyncStateEvent`].
58    ///
59    /// A [`AnySyncTimelineEvent`] can represent either message-like events or
60    /// state events. The message-like events are filtered out.
61    pub fn collect_from_timeline(
62        raw_events: &[Raw<AnySyncTimelineEvent>],
63    ) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
64        super::collect(raw_events.iter().filter_map(|raw_event| {
65            // Only state events have a `state_key` field.
66            match raw_event.get_field::<&str>("state_key") {
67                Ok(Some(_)) => Some(raw_event.cast_ref()),
68                _ => None,
69            }
70        }))
71    }
72
73    /// Dispatch the sync state events.
74    ///
75    /// `raw_events` and `events` must be generated from [`collect`].
76    /// Events must be exactly the same list of events that are in
77    /// `raw_events`, but deserialised. We demand them here to avoid
78    /// deserialising multiple times.
79    ///
80    /// The `new_users` mutable reference allows to collect the new users for
81    /// this room.
82    #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
83    pub async fn dispatch<U>(
84        context: &mut Context,
85        (raw_events, events): (&[Raw<AnySyncStateEvent>], &[AnySyncStateEvent]),
86        room_info: &mut RoomInfo,
87        ambiguity_cache: &mut AmbiguityCache,
88        new_users: &mut U,
89        state_store: &BaseStateStore,
90    ) -> StoreResult<()>
91    where
92        U: NewUsers,
93    {
94        for (raw_event, event) in iter::zip(raw_events, events) {
95            match event {
96                AnySyncStateEvent::RoomMember(member) => {
97                    room_info.handle_state_event(event);
98
99                    dispatch_room_member(
100                        context,
101                        &room_info.room_id,
102                        member,
103                        ambiguity_cache,
104                        new_users,
105                    )
106                    .await?;
107                }
108
109                AnySyncStateEvent::RoomCreate(create) => {
110                    if super::is_create_event_valid(
111                        context,
112                        room_info.room_id(),
113                        create,
114                        state_store,
115                    ) {
116                        room_info.handle_state_event(event);
117                    } else {
118                        error!(
119                            room_id = ?room_info.room_id(),
120                            ?create,
121                            "`m.create.tombstone` event is invalid, it creates a loop"
122                        );
123
124                        // Do not add the event to `room_info`.
125                        // Do not add the event to `context.state_changes.state`.
126                        continue;
127                    }
128                }
129
130                AnySyncStateEvent::RoomTombstone(tombstone) => {
131                    if super::is_tombstone_event_valid(
132                        context,
133                        room_info.room_id(),
134                        tombstone,
135                        state_store,
136                    ) {
137                        room_info.handle_state_event(event);
138                    } else {
139                        error!(
140                            room_id = ?room_info.room_id(),
141                            ?tombstone,
142                            "`m.room.tombstone` event is invalid, it creates a loop"
143                        );
144
145                        // Do not add the event to `room_info`.
146                        // Do not add the event to `context.state_changes.state`.
147                        continue;
148                    }
149                }
150
151                _ => {
152                    room_info.handle_state_event(event);
153                }
154            }
155
156            context
157                .state_changes
158                .state
159                .entry(room_info.room_id.to_owned())
160                .or_default()
161                .entry(event.event_type())
162                .or_default()
163                .insert(event.state_key().to_owned(), raw_event.clone());
164        }
165
166        Ok(())
167    }
168
169    /// Dispatch a [`RoomMemberEventContent>`] state event.
170    async fn dispatch_room_member<U>(
171        context: &mut Context,
172        room_id: &RoomId,
173        event: &SyncStateEvent<RoomMemberEventContent>,
174        ambiguity_cache: &mut AmbiguityCache,
175        new_users: &mut U,
176    ) -> StoreResult<()>
177    where
178        U: NewUsers,
179    {
180        ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
181
182        match event.membership() {
183            MembershipState::Join | MembershipState::Invite => {
184                new_users.insert(event.state_key());
185            }
186            _ => (),
187        }
188
189        profiles::upsert_or_delete(context, room_id, event);
190
191        Ok(())
192    }
193
194    /// A trait to collect new users in [`dispatch`].
195    trait NewUsers {
196        /// Insert a new user in the collection of new users.
197        fn insert(&mut self, user_id: &UserId);
198    }
199
200    impl NewUsers for BTreeSet<OwnedUserId> {
201        fn insert(&mut self, user_id: &UserId) {
202            self.insert(user_id.to_owned());
203        }
204    }
205
206    impl NewUsers for () {
207        fn insert(&mut self, _user_id: &UserId) {}
208    }
209}
210
211/// Collect [`AnyStrippedStateEvent`].
212pub mod stripped {
213    use std::{collections::BTreeMap, iter};
214
215    use ruma::{events::AnyStrippedStateEvent, push::Action};
216    use tracing::instrument;
217
218    use super::{
219        super::{notification, timeline},
220        Context, Raw,
221    };
222    use crate::{Result, Room, RoomInfo};
223
224    /// Collect [`AnyStrippedStateEvent`] to [`AnyStrippedStateEvent`].
225    pub fn collect(
226        raw_events: &[Raw<AnyStrippedStateEvent>],
227    ) -> (Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>) {
228        super::collect(raw_events)
229    }
230
231    /// Dispatch the stripped state events.
232    ///
233    /// `raw_events` and `events` must be generated from [`collect`].
234    /// Events must be exactly the same list of events that are in
235    /// `raw_events`, but deserialised. We demand them here to avoid
236    /// deserialising multiple times.
237    ///
238    /// Dispatch the stripped state events in `invite_state` or `knock_state`,
239    /// modifying the room's info and posting notifications as needed.
240    ///
241    /// * `raw_events` and `events` - The contents of `invite_state` in the form
242    ///   of list of pairs of raw stripped state events with their deserialized
243    ///   counterpart.
244    /// * `room` - The [`Room`] to modify.
245    /// * `room_info` - The current room's info.
246    /// * `notifications` - Notifications to post for the current room.
247    #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
248    pub(crate) async fn dispatch_invite_or_knock(
249        context: &mut Context,
250        (raw_events, events): (&[Raw<AnyStrippedStateEvent>], &[AnyStrippedStateEvent]),
251        room: &Room,
252        room_info: &mut RoomInfo,
253        mut notification: notification::Notification<'_>,
254    ) -> Result<()> {
255        let mut state_events = BTreeMap::new();
256
257        for (raw_event, event) in iter::zip(raw_events, events) {
258            room_info.handle_stripped_state_event(event);
259            state_events
260                .entry(event.event_type())
261                .or_insert_with(BTreeMap::new)
262                .insert(event.state_key().to_owned(), raw_event.clone());
263        }
264
265        context
266            .state_changes
267            .stripped_state
268            .insert(room_info.room_id().to_owned(), state_events.clone());
269
270        // We need to check for notifications after we have handled all state
271        // events, to make sure we have the full push context.
272        if let Some(push_condition_room_ctx) =
273            timeline::get_push_room_context(context, room, room_info, notification.state_store)
274                .await?
275        {
276            let room_id = room.room_id();
277
278            // Check every event again for notification.
279            for event in state_events.values().flat_map(|map| map.values()) {
280                notification.push_notification_from_event_if(
281                    room_id,
282                    &push_condition_room_ctx,
283                    event,
284                    Action::should_notify,
285                );
286            }
287        }
288
289        Ok(())
290    }
291}
292
293fn collect<'a, I, T>(raw_events: I) -> (Vec<Raw<T>>, Vec<T>)
294where
295    I: IntoIterator<Item = &'a Raw<T>>,
296    T: Deserialize<'a> + 'a,
297{
298    raw_events
299        .into_iter()
300        .filter_map(|raw_event| match raw_event.deserialize() {
301            Ok(event) => Some((raw_event.clone(), event)),
302            Err(e) => {
303                warn!("Couldn't deserialize stripped state event: {e}");
304                None
305            }
306        })
307        .unzip()
308}
309
310/// Check if `m.room.create` isn't creating a loop of rooms.
311pub fn is_create_event_valid(
312    context: &mut Context,
313    room_id: &RoomId,
314    event: &SyncStateEvent<RoomCreateEventContent>,
315    state_store: &BaseStateStore,
316) -> bool {
317    let mut already_seen = BTreeSet::new();
318    already_seen.insert(room_id.to_owned());
319
320    let Some(mut predecessor_room_id) = event
321        .as_original()
322        .and_then(|event| Some(event.content.predecessor.as_ref()?.room_id.clone()))
323    else {
324        // `true` means no problem. No predecessor = no problem here.
325        return true;
326    };
327
328    loop {
329        // We must check immediately if the `predecessor_room_id` is in `already_seen`
330        // in case of a room is created and marks itself as its predecessor in a single
331        // sync.
332        if already_seen.contains(AsRef::<RoomId>::as_ref(&predecessor_room_id)) {
333            // Ahhh, there is a loop with `m.room.create` events!
334            return false;
335        }
336
337        already_seen.insert(predecessor_room_id.clone());
338
339        // Where is the predecessor room? Check in `room_infos` and then in
340        // `state_store`.
341        let Some(next_predecessor_room_id) = context
342            .state_changes
343            .room_infos
344            .get(&predecessor_room_id)
345            .and_then(|room_info| Some(room_info.create()?.predecessor.as_ref()?.room_id.clone()))
346            .or_else(|| {
347                state_store
348                    .room(&predecessor_room_id)
349                    .and_then(|room| Some(room.predecessor_room()?.room_id))
350            })
351        else {
352            // No more predecessor found. Everything seems alright. No loop.
353            break;
354        };
355
356        predecessor_room_id = next_predecessor_room_id;
357    }
358
359    true
360}
361
362/// Check if `m.room.tombstone` isn't creating a loop of rooms.
363pub fn is_tombstone_event_valid(
364    context: &mut Context,
365    room_id: &RoomId,
366    event: &SyncStateEvent<RoomTombstoneEventContent>,
367    state_store: &BaseStateStore,
368) -> bool {
369    let mut already_seen = BTreeSet::new();
370    already_seen.insert(room_id.to_owned());
371
372    let Some(mut successor_room_id) =
373        event.as_original().map(|event| event.content.replacement_room.clone())
374    else {
375        // `true` means no problem. No successor = no problem here.
376        return true;
377    };
378
379    loop {
380        // We must check immediately if the `successor_room_id` is in `already_seen` in
381        // case of a room is created and tombstones itself in a single sync.
382        if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
383            // Ahhh, there is a loop with `m.room.tombstone` events!
384            return false;
385        }
386
387        already_seen.insert(successor_room_id.clone());
388
389        // Where is the successor room? Check in `room_infos` and then in `state_store`.
390        let Some(next_successor_room_id) = context
391            .state_changes
392            .room_infos
393            .get(&successor_room_id)
394            .and_then(|room_info| Some(room_info.tombstone()?.replacement_room.clone()))
395            .or_else(|| {
396                state_store
397                    .room(&successor_room_id)
398                    .and_then(|room| Some(room.successor_room()?.room_id))
399            })
400        else {
401            // No more successor found. Everything seems alright. No loop.
402            break;
403        };
404
405        successor_room_id = next_successor_room_id;
406    }
407
408    true
409}
410
411#[cfg(test)]
412mod tests {
413    use matrix_sdk_test::{
414        async_test, event_factory::EventFactory, JoinedRoomBuilder, StateTestEvent,
415        SyncResponseBuilder, DEFAULT_TEST_ROOM_ID,
416    };
417    use ruma::{event_id, room_id, user_id, RoomVersionId};
418
419    use crate::test_utils::logged_in_base_client;
420
421    #[async_test]
422    async fn test_not_possible_to_overwrite_m_room_create() {
423        let sender = user_id!("@mnt_io:matrix.org");
424        let event_factory = EventFactory::new().sender(sender);
425        let mut response_builder = SyncResponseBuilder::new();
426        let room_id_0 = room_id!("!r0");
427        let room_id_1 = room_id!("!r1");
428        let room_id_2 = room_id!("!r2");
429
430        let client = logged_in_base_client(None).await;
431
432        // Create room 0 with 2 `m.room.create` events.
433        // Create room 1 with 1 `m.room.create` event.
434        // Create room 2 with 0 `m.room.create` event.
435        {
436            let response = response_builder
437                .add_joined_room(
438                    JoinedRoomBuilder::new(room_id_0)
439                        .add_timeline_event(
440                            event_factory.create(sender, RoomVersionId::try_from("42").unwrap()),
441                        )
442                        .add_timeline_event(
443                            event_factory.create(sender, RoomVersionId::try_from("43").unwrap()),
444                        ),
445                )
446                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
447                    event_factory.create(sender, RoomVersionId::try_from("44").unwrap()),
448                ))
449                .add_joined_room(JoinedRoomBuilder::new(room_id_2))
450                .build_sync_response();
451
452            assert!(client.receive_sync_response(response).await.is_ok());
453
454            // Room 0
455            // the second `m.room.create` has been ignored!
456            assert_eq!(
457                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
458                "42"
459            );
460            // Room 1
461            assert_eq!(
462                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
463                "44"
464            );
465            // Room 2
466            assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
467        }
468
469        // Room 0 receives a new `m.room.create` event.
470        // Room 1 receives a new `m.room.create` event.
471        // Room 2 receives its first `m.room.create` event.
472        {
473            let response = response_builder
474                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
475                    event_factory.create(sender, RoomVersionId::try_from("45").unwrap()),
476                ))
477                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
478                    event_factory.create(sender, RoomVersionId::try_from("46").unwrap()),
479                ))
480                .add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
481                    event_factory.create(sender, RoomVersionId::try_from("47").unwrap()),
482                ))
483                .build_sync_response();
484
485            assert!(client.receive_sync_response(response).await.is_ok());
486
487            // Room 0
488            // the third `m.room.create` has been ignored!
489            assert_eq!(
490                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
491                "42"
492            );
493            // Room 1
494            // the second `m.room.create` has been ignored!
495            assert_eq!(
496                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
497                "44"
498            );
499            // Room 2
500            assert_eq!(
501                client.get_room(room_id_2).unwrap().create_content().unwrap().room_version.as_str(),
502                "47"
503            );
504        }
505    }
506
507    #[async_test]
508    async fn test_check_room_upgrades_no_newly_tombstoned_rooms() {
509        let client = logged_in_base_client(None).await;
510
511        // Create a new room, no tombstone, no anything.
512        {
513            let response = SyncResponseBuilder::new()
514                .add_joined_room(JoinedRoomBuilder::new(room_id!("!r0")))
515                .build_sync_response();
516
517            assert!(client.receive_sync_response(response).await.is_ok());
518        }
519    }
520
521    #[async_test]
522    async fn test_check_room_upgrades_no_error() {
523        let sender = user_id!("@mnt_io:matrix.org");
524        let event_factory = EventFactory::new().sender(sender);
525        let mut response_builder = SyncResponseBuilder::new();
526        let room_id_0 = room_id!("!r0");
527        let room_id_1 = room_id!("!r1");
528        let room_id_2 = room_id!("!r2");
529
530        let client = logged_in_base_client(None).await;
531
532        // Room 0.
533        {
534            let response = response_builder
535                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
536                    // Room 0 has no predecessor.
537                    event_factory.create(sender, RoomVersionId::try_from("41").unwrap()),
538                ))
539                .build_sync_response();
540
541            assert!(client.receive_sync_response(response).await.is_ok());
542
543            let room_0 = client.get_room(room_id_0).unwrap();
544
545            assert!(room_0.predecessor_room().is_none());
546            assert!(room_0.successor_room().is_none());
547        }
548
549        // Room 0 and room 1.
550        {
551            let tombstone_event_id = event_id!("$ev0");
552            let response = response_builder
553                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
554                    // Successor of room 0 is room 1.
555                    event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
556                ))
557                .add_joined_room(
558                    JoinedRoomBuilder::new(room_id_1).add_timeline_event(
559                        // Predecessor of room 1 is room 0.
560                        event_factory
561                            .create(sender, RoomVersionId::try_from("42").unwrap())
562                            .predecessor(room_id_0, tombstone_event_id),
563                    ),
564                )
565                .build_sync_response();
566
567            assert!(client.receive_sync_response(response).await.is_ok());
568
569            let room_0 = client.get_room(room_id_0).unwrap();
570
571            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
572            assert_eq!(
573                room_0.successor_room().expect("room 0 must have a successor").room_id,
574                room_id_1,
575                "room 0 does not have the expected successor",
576            );
577
578            let room_1 = client.get_room(room_id_1).unwrap();
579
580            assert_eq!(
581                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
582                room_id_0,
583                "room 1 does not have the expected predecessor",
584            );
585            assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
586        }
587
588        // Room 1 and room 2.
589        {
590            let tombstone_event_id = event_id!("$ev1");
591            let response = response_builder
592                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
593                    // Successor of room 1 is room 2.
594                    event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
595                ))
596                .add_joined_room(
597                    JoinedRoomBuilder::new(room_id_2).add_timeline_event(
598                        // Predecessor of room 2 is room 1.
599                        event_factory
600                            .create(sender, RoomVersionId::try_from("43").unwrap())
601                            .predecessor(room_id_1, tombstone_event_id),
602                    ),
603                )
604                .build_sync_response();
605
606            assert!(client.receive_sync_response(response).await.is_ok());
607
608            let room_1 = client.get_room(room_id_1).unwrap();
609
610            assert_eq!(
611                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
612                room_id_0,
613                "room 1 does not have the expected predecessor",
614            );
615            assert_eq!(
616                room_1.successor_room().expect("room 1 must have a successor").room_id,
617                room_id_2,
618                "room 1 does not have the expected successor",
619            );
620
621            let room_2 = client.get_room(room_id_2).unwrap();
622
623            assert_eq!(
624                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
625                room_id_1,
626                "room 2 does not have the expected predecessor",
627            );
628            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
629        }
630    }
631
632    #[async_test]
633    async fn test_check_room_upgrades_no_loop_within_misordered_rooms() {
634        let sender = user_id!("@mnt_io:matrix.org");
635        let event_factory = EventFactory::new().sender(sender);
636        let mut response_builder = SyncResponseBuilder::new();
637        // The room IDs are important because `SyncResponseBuilder` stores them in a
638        // `HashMap`, so they are going to be “shuffled”.
639        let room_id_0 = room_id!("!r1");
640        let room_id_1 = room_id!("!r0");
641        let room_id_2 = room_id!("!r2");
642
643        let client = logged_in_base_client(None).await;
644
645        // Create all rooms in a misordered way to see if `check_tombstone` will
646        // re-order them appropriately.
647        {
648            let response = response_builder
649                // Room 0
650                .add_joined_room(
651                    JoinedRoomBuilder::new(room_id_0)
652                        .add_timeline_event(
653                            // No predecessor for room 0.
654                            event_factory.create(sender, RoomVersionId::try_from("41").unwrap()),
655                        )
656                        .add_timeline_event(
657                            // Successor of room 0 is room 1.
658                            event_factory
659                                .room_tombstone("hello", room_id_1)
660                                .event_id(event_id!("$ev0")),
661                        ),
662                )
663                // Room 1
664                .add_joined_room(
665                    JoinedRoomBuilder::new(room_id_1)
666                        .add_timeline_event(
667                            // Predecessor of room 1 is room 0.
668                            event_factory
669                                .create(sender, RoomVersionId::try_from("42").unwrap())
670                                .predecessor(room_id_0, event_id!("$ev0")),
671                        )
672                        .add_timeline_event(
673                            // Successor of room 1 is room 2.
674                            event_factory
675                                .room_tombstone("hello", room_id_2)
676                                .event_id(event_id!("$ev1")),
677                        ),
678                )
679                // Room 2
680                .add_joined_room(
681                    JoinedRoomBuilder::new(room_id_2).add_timeline_event(
682                        // Predecessor of room 2 is room 1.
683                        event_factory
684                            .create(sender, RoomVersionId::try_from("43").unwrap())
685                            .predecessor(room_id_1, event_id!("$ev1")),
686                    ),
687                )
688                .build_sync_response();
689
690            // At this point, we can check that `response` contains misordered room updates.
691            {
692                let mut rooms = response.rooms.join.keys();
693
694                // Room 1 is before room 0!
695                assert_eq!(rooms.next().unwrap(), room_id_1);
696                assert_eq!(rooms.next().unwrap(), room_id_0);
697                assert_eq!(rooms.next().unwrap(), room_id_2);
698                assert!(rooms.next().is_none());
699            }
700
701            // But the algorithm to detect invalid states works nicely.
702            assert!(client.receive_sync_response(response).await.is_ok());
703
704            let room_0 = client.get_room(room_id_0).unwrap();
705
706            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
707            assert_eq!(
708                room_0.successor_room().expect("room 0 must have a successor").room_id,
709                room_id_1,
710                "room 0 does not have the expected successor",
711            );
712
713            let room_1 = client.get_room(room_id_1).unwrap();
714
715            assert_eq!(
716                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
717                room_id_0,
718                "room 1 does not have the expected predecessor",
719            );
720            assert_eq!(
721                room_1.successor_room().expect("room 1 must have a successor").room_id,
722                room_id_2,
723                "room 1 does not have the expected successor",
724            );
725
726            let room_2 = client.get_room(room_id_2).unwrap();
727
728            assert_eq!(
729                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
730                room_id_1,
731                "room 2 does not have the expected predecessor",
732            );
733            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
734        }
735    }
736
737    #[async_test]
738    async fn test_check_room_upgrades_shortest_invalid_successor() {
739        let sender = user_id!("@mnt_io:matrix.org");
740        let event_factory = EventFactory::new().sender(sender);
741        let mut response_builder = SyncResponseBuilder::new();
742        let room_id_0 = room_id!("!r0");
743
744        let client = logged_in_base_client(None).await;
745
746        // Room 0.
747        {
748            let tombstone_event_id = event_id!("$ev0");
749            let response = response_builder
750                .add_joined_room(
751                    // Successor of room 0 is room 0.
752                    // No predecessor.
753                    JoinedRoomBuilder::new(room_id_0).add_timeline_event(
754                        event_factory
755                            .room_tombstone("hello", room_id_0)
756                            .event_id(tombstone_event_id),
757                    ),
758                )
759                .build_sync_response();
760
761            // The sync doesn't fail but…
762            assert!(client.receive_sync_response(response).await.is_ok());
763
764            // … the state event has not been saved.
765            let room_0 = client.get_room(room_id_0).unwrap();
766
767            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
768            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
769        }
770    }
771
772    #[async_test]
773    async fn test_check_room_upgrades_invalid_successor() {
774        let sender = user_id!("@mnt_io:matrix.org");
775        let event_factory = EventFactory::new().sender(sender);
776        let mut response_builder = SyncResponseBuilder::new();
777        let room_id_0 = room_id!("!r0");
778        let room_id_1 = room_id!("!r1");
779        let room_id_2 = room_id!("!r2");
780
781        let client = logged_in_base_client(None).await;
782
783        // Room 0 and room 1.
784        {
785            let tombstone_event_id = event_id!("$ev0");
786            let response = response_builder
787                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
788                    // Successor of room 0 is room 1.
789                    event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
790                ))
791                .add_joined_room(
792                    JoinedRoomBuilder::new(room_id_1).add_timeline_event(
793                        // Predecessor of room 1 is room 0.
794                        event_factory
795                            .create(sender, RoomVersionId::try_from("42").unwrap())
796                            .predecessor(room_id_0, tombstone_event_id),
797                    ),
798                )
799                .build_sync_response();
800
801            assert!(client.receive_sync_response(response).await.is_ok());
802
803            let room_0 = client.get_room(room_id_0).unwrap();
804
805            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
806            assert_eq!(
807                room_0.successor_room().expect("room 0 must have a successor").room_id,
808                room_id_1,
809                "room 0 does not have the expected successor",
810            );
811
812            let room_1 = client.get_room(room_id_1).unwrap();
813
814            assert_eq!(
815                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
816                room_id_0,
817                "room 1 does not have the expected predecessor",
818            );
819            assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
820        }
821
822        // Room 1, room 2 and room 0.
823        {
824            let tombstone_event_id = event_id!("$ev1");
825            let response = response_builder
826                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
827                    // Successor of room 1 is room 2.
828                    event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
829                ))
830                .add_joined_room(
831                    JoinedRoomBuilder::new(room_id_2)
832                        .add_timeline_event(
833                            // Predecessor of room 2 is room 1.
834                            event_factory
835                                .create(sender, RoomVersionId::try_from("43").unwrap())
836                                .predecessor(room_id_1, tombstone_event_id),
837                        )
838                        .add_timeline_event(
839                            // Successor of room 2 is room 0.
840                            event_factory
841                                .room_tombstone("hehe", room_id_0)
842                                .event_id(event_id!("$ev_foo")),
843                        ),
844                )
845                .build_sync_response();
846
847            // The sync doesn't fail but…
848            assert!(client.receive_sync_response(response).await.is_ok());
849
850            // … the state event for `room_id_2` has not been saved.
851            let room_0 = client.get_room(room_id_0).unwrap();
852
853            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
854            assert_eq!(
855                room_0.successor_room().expect("room 0 must have a successor").room_id,
856                room_id_1,
857                "room 0 does not have the expected successor",
858            );
859
860            let room_1 = client.get_room(room_id_1).unwrap();
861
862            assert_eq!(
863                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
864                room_id_0,
865                "room 1 does not have the expected predecessor",
866            );
867            assert_eq!(
868                room_1.successor_room().expect("room 1 must have a successor").room_id,
869                room_id_2,
870                "room 1 does not have the expected successor",
871            );
872
873            let room_2 = client.get_room(room_id_2).unwrap();
874
875            assert_eq!(
876                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
877                room_id_1,
878                "room 2 does not have the expected predecessor",
879            );
880            // this state event is missing because it creates a loop
881            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
882        }
883    }
884
885    #[async_test]
886    async fn test_check_room_upgrades_shortest_invalid_predecessor() {
887        let sender = user_id!("@mnt_io:matrix.org");
888        let event_factory = EventFactory::new().sender(sender);
889        let mut response_builder = SyncResponseBuilder::new();
890        let room_id_0 = room_id!("!r0");
891
892        let client = logged_in_base_client(None).await;
893
894        // Room 0.
895        {
896            let tombstone_event_id = event_id!("$ev0");
897            let response = response_builder
898                .add_joined_room(
899                    // Predecessor of room 0 is room 0.
900                    // No successor.
901                    JoinedRoomBuilder::new(room_id_0).add_timeline_event(
902                        event_factory
903                            .create(sender, RoomVersionId::try_from("42").unwrap())
904                            .predecessor(room_id_0, tombstone_event_id)
905                            .event_id(tombstone_event_id),
906                    ),
907                )
908                .build_sync_response();
909
910            // The sync doesn't fail but…
911            assert!(client.receive_sync_response(response).await.is_ok());
912
913            // … the state event has not been saved.
914            let room_0 = client.get_room(room_id_0).unwrap();
915
916            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
917            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
918        }
919    }
920
921    #[async_test]
922    async fn test_check_room_upgrades_shortest_loop() {
923        let sender = user_id!("@mnt_io:matrix.org");
924        let event_factory = EventFactory::new().sender(sender);
925        let mut response_builder = SyncResponseBuilder::new();
926        let room_id_0 = room_id!("!r0");
927
928        let client = logged_in_base_client(None).await;
929
930        // Room 0.
931        {
932            let tombstone_event_id = event_id!("$ev0");
933            let response = response_builder
934                .add_joined_room(
935                    JoinedRoomBuilder::new(room_id_0)
936                        .add_timeline_event(
937                            // Successor of room 0 is room 0
938                            event_factory
939                                .room_tombstone("hello", room_id_0)
940                                .event_id(tombstone_event_id),
941                        )
942                        .add_timeline_event(
943                            // Predecessor of room 0 is room 0
944                            event_factory
945                                .create(sender, RoomVersionId::try_from("42").unwrap())
946                                .predecessor(room_id_0, tombstone_event_id),
947                        ),
948                )
949                .build_sync_response();
950
951            // The sync doesn't fail but…
952            assert!(client.receive_sync_response(response).await.is_ok());
953
954            // … the state event has not been saved.
955            let room_0 = client.get_room(room_id_0).unwrap();
956
957            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
958            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
959        }
960    }
961
962    #[async_test]
963    async fn test_check_room_upgrades_loop() {
964        let sender = user_id!("@mnt_io:matrix.org");
965        let event_factory = EventFactory::new().sender(sender);
966        let mut response_builder = SyncResponseBuilder::new();
967        let room_id_0 = room_id!("!r0");
968        let room_id_1 = room_id!("!r1");
969        let room_id_2 = room_id!("!r2");
970
971        let client = logged_in_base_client(None).await;
972
973        // Room 0, room 1 and room 2.
974        //
975        // Doing that in one sync, it's the only way to create such loop (otherwise it
976        // implies overwriting the `m.room.create` event, or not setting it first, then
977        // setting it later… anyway, it works in one sync)
978        {
979            let response = response_builder
980                .add_joined_room(
981                    JoinedRoomBuilder::new(room_id_0)
982                        .add_timeline_event(
983                            // Predecessor of room 0 is room 2
984                            event_factory
985                                .create(sender, RoomVersionId::try_from("42").unwrap())
986                                .predecessor(room_id_2, event_id!("$ev2")),
987                        )
988                        .add_timeline_event(
989                            // Successor of room 0 is room 1
990                            event_factory
991                                .room_tombstone("hello", room_id_1)
992                                .event_id(event_id!("$ev0")),
993                        ),
994                )
995                .add_joined_room(
996                    JoinedRoomBuilder::new(room_id_1)
997                        .add_timeline_event(
998                            // Predecessor of room 1 is room 0
999                            event_factory
1000                                .create(sender, RoomVersionId::try_from("43").unwrap())
1001                                .predecessor(room_id_0, event_id!("$ev0")),
1002                        )
1003                        .add_timeline_event(
1004                            // Successor of room 1 is room 2
1005                            event_factory
1006                                .room_tombstone("hello", room_id_2)
1007                                .event_id(event_id!("$ev1")),
1008                        ),
1009                )
1010                .add_joined_room(
1011                    JoinedRoomBuilder::new(room_id_2)
1012                        .add_timeline_event(
1013                            // Predecessor of room 2 is room 1
1014                            event_factory
1015                                .create(sender, RoomVersionId::try_from("44").unwrap())
1016                                .predecessor(room_id_1, event_id!("$ev1")),
1017                        )
1018                        .add_timeline_event(
1019                            // Successor of room 2 is room 0
1020                            event_factory
1021                                .room_tombstone("hello", room_id_0)
1022                                .event_id(event_id!("$ev2")),
1023                        ),
1024                )
1025                .build_sync_response();
1026
1027            // The sync doesn't fail but…
1028            assert!(client.receive_sync_response(response).await.is_ok());
1029
1030            // … the state event for room 2 -> room 0 has not been saved, but room 0 <- room
1031            // 2 has been saved.
1032            let room_0 = client.get_room(room_id_0).unwrap();
1033
1034            assert_eq!(
1035                room_0.predecessor_room().expect("room 0 must have a predecessor").room_id,
1036                room_id_2,
1037                "room 0 does not have the expected predecessor"
1038            );
1039            assert_eq!(
1040                room_0.successor_room().expect("room 0 must have a successor").room_id,
1041                room_id_1,
1042                "room 0 does not have the expected successor",
1043            );
1044
1045            let room_1 = client.get_room(room_id_1).unwrap();
1046
1047            assert_eq!(
1048                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
1049                room_id_0,
1050                "room 1 does not have the expected predecessor",
1051            );
1052            assert_eq!(
1053                room_1.successor_room().expect("room 1 must have a successor").room_id,
1054                room_id_2,
1055                "room 1 does not have the expected successor",
1056            );
1057
1058            let room_2 = client.get_room(room_id_2).unwrap();
1059
1060            // this state event is missing because it creates a loop
1061            assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
1062            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1063        }
1064    }
1065
1066    #[async_test]
1067    async fn test_state_events_after_sync() {
1068        // Given a room
1069        let user_id = user_id!("@u:u.to");
1070
1071        let client = logged_in_base_client(Some(user_id)).await;
1072        let mut sync_builder = SyncResponseBuilder::new();
1073
1074        let room_name = EventFactory::new()
1075            .sender(user_id)
1076            .room_topic("this is the test topic in the timeline")
1077            .event_id(event_id!("$2"))
1078            .into_raw_sync();
1079
1080        let response = sync_builder
1081            .add_joined_room(
1082                JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
1083                    .add_timeline_event(room_name)
1084                    .add_state_event(StateTestEvent::PowerLevels),
1085            )
1086            .build_sync_response();
1087        client.receive_sync_response(response).await.unwrap();
1088
1089        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
1090
1091        // ensure that we have the power levels
1092        assert!(room.power_levels().await.is_ok());
1093
1094        // ensure that we have the topic
1095        assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
1096    }
1097}