Skip to main content

matrix_sdk_base/
sliding_sync.rs

1// Copyright 2023 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
15//! Extend `BaseClient` with capabilities to handle MSC4186.
16
17#[cfg(feature = "e2e-encryption")]
18use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
19use matrix_sdk_common::timer;
20use ruma::{
21    OwnedRoomId, api::client::sync::sync_events::v5 as http, events::receipt::SyncReceiptEvent,
22    serde::Raw,
23};
24use tokio::sync::MutexGuard;
25use tracing::{instrument, trace};
26
27use super::BaseClient;
28use crate::{
29    RequestedRequiredStates,
30    error::Result,
31    response_processors as processors,
32    store::{AvatarCache, ambiguity_map::AmbiguityCache},
33    sync::{RoomUpdates, SyncResponse},
34};
35
36impl BaseClient {
37    /// Processes the E2EE-related events from the Sliding Sync response.
38    ///
39    /// In addition to writes to the crypto store, this may also write into the
40    /// state store, in particular it may write latest-events to the state
41    /// store.
42    ///
43    /// Returns whether any change happened.
44    #[cfg(feature = "e2e-encryption")]
45    pub async fn process_sliding_sync_e2ee(
46        &self,
47        to_device: Option<&http::response::ToDevice>,
48        e2ee: &http::response::E2EE,
49        state_store_guard: &MutexGuard<'_, ()>,
50    ) -> Result<Option<Vec<ProcessedToDeviceEvent>>> {
51        if to_device.is_none() && e2ee.is_empty() {
52            return Ok(None);
53        }
54
55        trace!(
56            to_device_events =
57                to_device.map(|to_device| to_device.events.len()).unwrap_or_default(),
58            device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
59            device_unused_fallback_key_types =
60                e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
61            "Processing sliding sync e2ee events",
62        );
63
64        let olm_machine = self.olm_machine().await;
65
66        let context = processors::Context::default();
67
68        let processors::e2ee::to_device::Output { processed_to_device_events } =
69            processors::e2ee::to_device::from_msc4186(
70                to_device,
71                e2ee,
72                olm_machine.as_ref(),
73                &self.decryption_settings,
74            )
75            .await?;
76
77        processors::changes::save_and_apply(
78            context,
79            &self.state_store,
80            state_store_guard,
81            &self.ignore_user_list_changes,
82            None,
83        )
84        .await?;
85
86        Ok(Some(processed_to_device_events))
87    }
88
89    /// Process a response from a sliding sync call.
90    ///
91    /// # Arguments
92    ///
93    /// * `response` - The response that we received after a successful sliding
94    ///   sync.
95    #[instrument(skip_all, level = "trace")]
96    pub async fn process_sliding_sync(
97        &self,
98        response: &http::Response,
99        requested_required_states: &RequestedRequiredStates,
100        state_store_guard: &MutexGuard<'_, ()>,
101    ) -> Result<SyncResponse> {
102        let http::Response { rooms, lists, extensions, .. } = response;
103
104        trace!(
105            rooms = rooms.len(),
106            lists = lists.len(),
107            has_extensions = !extensions.is_empty(),
108            "Processing sliding sync room events"
109        );
110
111        if rooms.is_empty() && extensions.is_empty() {
112            // we received a room reshuffling event only, there won't be anything for us to
113            // process. stop early
114            return Ok(SyncResponse::default());
115        }
116
117        let _timer = timer!(tracing::Level::TRACE, "_method");
118
119        let mut context = processors::Context::default();
120
121        let state_store = self.state_store.clone();
122        let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
123        let mut avatar_cache = AvatarCache::new(state_store.inner.clone());
124
125        let global_account_data_processor =
126            processors::account_data::global(&extensions.account_data.global);
127        let push_rules = self.get_push_rules(&global_account_data_processor).await?;
128
129        let mut room_updates = RoomUpdates::default();
130        let mut notifications = Default::default();
131
132        let user_id = self
133            .session_meta()
134            .expect("Sliding sync shouldn't run without an authenticated user")
135            .user_id
136            .to_owned();
137
138        for (room_id, room_response) in rooms {
139            let Some((room_info, room_update)) = processors::room::msc4186::update_any_room(
140                &mut context,
141                &user_id,
142                processors::room::RoomCreationData::new(
143                    room_id,
144                    requested_required_states,
145                    &mut ambiguity_cache,
146                    &mut avatar_cache,
147                ),
148                room_response,
149                &extensions.account_data.rooms,
150                #[cfg(feature = "e2e-encryption")]
151                processors::e2ee::E2EE::new(
152                    self.olm_machine().await.as_ref(),
153                    &self.decryption_settings,
154                    self.handle_verification_events,
155                ),
156                processors::notification::Notification::new(
157                    &push_rules,
158                    &mut notifications,
159                    &self.state_store,
160                ),
161            )
162            .await?
163            else {
164                continue;
165            };
166
167            context.state_changes.add_room(room_info);
168
169            let room_id = room_id.to_owned();
170
171            use processors::room::msc4186::RoomUpdateKind;
172
173            match room_update {
174                RoomUpdateKind::Joined(joined_room_update) => {
175                    room_updates.joined.insert(room_id, joined_room_update);
176                }
177                RoomUpdateKind::Left(left_room_update) => {
178                    room_updates.left.insert(room_id, left_room_update);
179                }
180                RoomUpdateKind::Invited(invited_room_update) => {
181                    room_updates.invited.insert(room_id, invited_room_update);
182                }
183                RoomUpdateKind::Knocked(knocked_room_update) => {
184                    room_updates.knocked.insert(room_id, knocked_room_update);
185                }
186            }
187        }
188
189        // Handle read receipts and typing notifications independently of the rooms:
190        // these both live in a different subsection of the server's response,
191        // so they may exist without any update for the associated room.
192        processors::room::msc4186::extensions::dispatch_typing_ephemeral_events(
193            &extensions.typing,
194            &mut room_updates.joined,
195        );
196
197        // Handle room account data.
198        processors::room::msc4186::extensions::room_account_data(
199            &mut context,
200            &extensions.account_data,
201            &mut room_updates,
202            &self.state_store,
203        );
204
205        global_account_data_processor.apply(&mut context, &state_store).await;
206
207        context.state_changes.ambiguity_maps = ambiguity_cache.cache;
208
209        // Persist any global profile updates received through the profiles extension.
210        context.state_changes.global_profiles = extensions.profiles.clone();
211
212        // Save the changes and apply them.
213        processors::changes::save_and_apply(
214            context,
215            &self.state_store,
216            state_store_guard,
217            &self.ignore_user_list_changes,
218            None,
219        )
220        .await?;
221
222        let mut context = processors::Context::default();
223
224        // Now that all the rooms information have been saved, update the display name
225        // of the updated rooms (which relies on information stored in the database).
226        processors::room::display_name::update_for_rooms(
227            &mut context,
228            &room_updates,
229            &self.state_store,
230        )
231        .await;
232
233        // Save the new display name updates if any.
234        processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
235
236        Ok(SyncResponse {
237            rooms: room_updates,
238            notifications,
239            presence: Default::default(),
240            account_data: extensions.account_data.global.clone(),
241            to_device: Default::default(),
242        })
243    }
244
245    /// Process the `receipts` extension, and compute (and save) the unread
246    /// counts based on read receipts, for a particular room.
247    #[doc(hidden)]
248    pub async fn process_sliding_sync_receipts_extension_for_room(
249        &self,
250        room_id: &OwnedRoomId,
251        response: &http::Response,
252        state_store_guard: &MutexGuard<'_, ()>,
253    ) -> Result<Option<Raw<SyncReceiptEvent>>> {
254        let mut context = processors::Context::default();
255
256        let mut save_context = false;
257
258        // Handle the receipt ephemeral event.
259        let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
260            response.extensions.receipts.rooms.get(room_id)
261        {
262            processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
263                &mut context,
264                room_id,
265                receipt_ephemeral_event,
266            );
267            save_context = true;
268            Some(receipt_ephemeral_event.clone())
269        } else {
270            None
271        };
272
273        // Save the new `RoomInfo` if updated.
274        if save_context {
275            processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
276        }
277
278        Ok(receipt_ephemeral_event)
279    }
280}
281
282#[cfg(all(test, not(target_family = "wasm")))]
283mod tests {
284    use std::collections::{BTreeMap, HashSet};
285
286    use assert_matches::assert_matches;
287    use matrix_sdk_test::async_test;
288    use ruma::{
289        JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
290        api::client::sync::sync_events::UnreadNotificationsCount,
291        assign, event_id,
292        events::{
293            GlobalAccountDataEventContent, StateEventContent, StateEventType,
294            direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
295            room::{
296                avatar::RoomAvatarEventContent,
297                canonical_alias::RoomCanonicalAliasEventContent,
298                encryption::RoomEncryptionEventContent,
299                member::{MembershipState, RoomMemberEventContent},
300                name::RoomNameEventContent,
301                pinned_events::RoomPinnedEventsEventContent,
302            },
303        },
304        mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
305        profile::UserProfileUpdate,
306        room_alias_id, room_id,
307        serde::Raw,
308        uint, user_id,
309    };
310    use serde_json::json;
311
312    use super::http;
313    use crate::{
314        BaseClient, DmRoomDefinition, EncryptionState, RequestedRequiredStates,
315        RoomInfoNotableUpdate, RoomState, SessionMeta,
316        client::ThreadingSupport,
317        room::{RoomHero, RoomInfoNotableUpdateReasons},
318        store::{RoomLoadSettings, StoreConfig},
319        test_utils::logged_in_base_client,
320    };
321
322    #[async_test]
323    async fn test_invited_state_without_update_emits_invited_room() {
324        let client = logged_in_base_client(None).await;
325        let room_id = room_id!("!invite:e.uk");
326        let user_id = client.session_meta().unwrap().user_id.to_owned();
327
328        let mut room = http::response::Room::new();
329        room.invite_state = Some(invite_state_for(&user_id, MembershipState::Invite));
330
331        let response = response_with_room(room_id, room);
332
333        let sync_resp = client
334            .process_sliding_sync(
335                &response,
336                &RequestedRequiredStates::default(),
337                &client.state_store_lock().lock().await,
338            )
339            .await
340            .unwrap();
341
342        assert!(sync_resp.rooms.invited.contains_key(room_id));
343    }
344
345    use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
346    use ruma::events::AnyStrippedStateEvent;
347
348    fn invite_state_for(
349        user_id: &UserId,
350        membership: MembershipState,
351    ) -> Vec<Raw<AnyStrippedStateEvent>> {
352        let content = RoomMemberEventContent::new(membership);
353
354        let raw: Raw<AnyStrippedStateEvent> = Raw::from_json_string(
355            serde_json::json!({
356                "type": "m.room.member",
357                "state_key": user_id,
358                "content": content,
359            })
360            .to_string(),
361        )
362        .unwrap();
363
364        vec![raw]
365    }
366
367    #[async_test]
368    async fn test_knocked_state_emits_invited_room() {
369        let client = logged_in_base_client(None).await;
370        let room_id = room_id!("!knock:e.uk");
371        let user_id = client.session_meta().unwrap().user_id.to_owned();
372
373        let mut room = http::response::Room::new();
374        room.invite_state = Some(invite_state_for(&user_id, MembershipState::Knock));
375
376        let response = response_with_room(room_id, room);
377
378        let sync_resp = client
379            .process_sliding_sync(
380                &response,
381                &RequestedRequiredStates::default(),
382                &client.state_store_lock().lock().await,
383            )
384            .await
385            .unwrap();
386
387        // ✅ Knocked rooms surface as invited rooms in Sliding Sync
388        assert!(sync_resp.rooms.invited.contains_key(room_id));
389    }
390
391    #[async_test]
392    async fn test_notification_count_set() {
393        let client = logged_in_base_client(None).await;
394
395        let mut response = http::Response::new("42".to_owned());
396        let room_id = room_id!("!room:example.org");
397        let count = assign!(UnreadNotificationsCount::default(), {
398            highlight_count: Some(uint!(13)),
399            notification_count: Some(uint!(37)),
400        });
401
402        response.rooms.insert(
403            room_id.to_owned(),
404            assign!(http::response::Room::new(), {
405                unread_notifications: count.clone()
406            }),
407        );
408
409        let sync_response = client
410            .process_sliding_sync(
411                &response,
412                &RequestedRequiredStates::default(),
413                &client.state_store_lock().lock().await,
414            )
415            .await
416            .expect("Failed to process sync");
417
418        // Check it's present in the response.
419        let room = sync_response.rooms.joined.get(room_id).unwrap();
420        assert_eq!(room.unread_notifications, count.clone().into());
421
422        // Check it's been updated in the store.
423        let room = client.get_room(room_id).expect("found room");
424        assert_eq!(room.unread_notification_counts(), count.into());
425    }
426
427    #[async_test]
428    async fn test_can_process_empty_sliding_sync_response() {
429        let client = logged_in_base_client(None).await;
430        let empty_response = http::Response::new("5".to_owned());
431        client
432            .process_sliding_sync(
433                &empty_response,
434                &RequestedRequiredStates::default(),
435                &client.state_store_lock().lock().await,
436            )
437            .await
438            .expect("Failed to process sync");
439    }
440
441    #[async_test]
442    async fn test_profiles_extension_is_persisted_from_sliding_sync() {
443        let client = logged_in_base_client(None).await;
444
445        let alice = user_id!("@alice:e.uk");
446        let bob = user_id!("@bob:e.uk");
447
448        // Given a sliding sync response carrying the profiles extension (MSC4262)
449        // for two users, and no rooms.
450        let mut response = http::Response::new("0".to_owned());
451        response.extensions.profiles.insert(
452            alice.to_owned(),
453            UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Alice"))]),
454        );
455        response.extensions.profiles.insert(
456            bob.to_owned(),
457            UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Bob"))]),
458        );
459
460        // When the response is processed.
461        client
462            .process_sliding_sync(
463                &response,
464                &RequestedRequiredStates::default(),
465                &client.state_store_lock().lock().await,
466            )
467            .await
468            .expect("Failed to process sync");
469
470        // Then both users' global profiles are persisted in the store.
471        let store = client.state_store();
472
473        let alice_profile = store
474            .get_global_profile(alice)
475            .await
476            .expect("Failed to read profile")
477            .expect("Alice's profile should be saved");
478        let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
479        assert_eq!(alice_map.get("displayname"), Some(&json!("Alice")));
480
481        let bob_profile = store
482            .get_global_profile(bob)
483            .await
484            .expect("Failed to read profile")
485            .expect("Bob's profile should be saved");
486        let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
487        assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
488
489        // When a subsequent response only carries an update for Alice.
490        let mut response = http::Response::new("1".to_owned());
491        response.extensions.profiles.insert(
492            alice.to_owned(),
493            UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Alice Updated"))]),
494        );
495
496        client
497            .process_sliding_sync(
498                &response,
499                &RequestedRequiredStates::default(),
500                &client.state_store_lock().lock().await,
501            )
502            .await
503            .expect("Failed to process sync");
504
505        // Then Alice's profile is updated, and Bob's remains unchanged.
506        let alice_profile = store
507            .get_global_profile(alice)
508            .await
509            .expect("Failed to read profile")
510            .expect("Alice's profile should be saved");
511        let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
512        assert_eq!(alice_map.get("displayname"), Some(&json!("Alice Updated")));
513
514        let bob_profile = store
515            .get_global_profile(bob)
516            .await
517            .expect("Failed to read profile")
518            .expect("Bob's profile should still be saved");
519        let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
520        assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
521    }
522
523    #[async_test]
524    async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
525        // Given a logged-in client
526        let client = logged_in_base_client(None).await;
527        let room_id = room_id!("!r:e.uk");
528
529        // When I send sliding sync response containing a room (with identifiable data
530        // in joined_count)
531        let mut room = http::response::Room::new();
532        room.joined_count = Some(uint!(41));
533        let response = response_with_room(room_id, room);
534        let sync_resp = client
535            .process_sliding_sync(
536                &response,
537                &RequestedRequiredStates::default(),
538                &client.state_store_lock().lock().await,
539            )
540            .await
541            .expect("Failed to process sync");
542
543        // Then the room appears in the client (with the same joined count)
544        let client_room = client.get_room(room_id).expect("No room found");
545        assert_eq!(client_room.room_id(), room_id);
546        assert_eq!(client_room.joined_members_count(), 41);
547        assert_eq!(client_room.state(), RoomState::Joined);
548
549        // And it is added to the list of joined rooms only.
550        assert!(sync_resp.rooms.joined.contains_key(room_id));
551        assert!(!sync_resp.rooms.left.contains_key(room_id));
552        assert!(!sync_resp.rooms.invited.contains_key(room_id));
553    }
554
555    #[async_test]
556    async fn test_missing_room_name_event() {
557        // Given a logged-in client
558        let client = logged_in_base_client(None).await;
559        let room_id = room_id!("!r:e.uk");
560
561        // When I send sliding sync response containing a room with a name set in the
562        // sliding sync response,
563        let mut room = http::response::Room::new();
564        room.name = Some("little room".to_owned());
565        let response = response_with_room(room_id, room);
566        let sync_resp = client
567            .process_sliding_sync(
568                &response,
569                &RequestedRequiredStates::default(),
570                &client.state_store_lock().lock().await,
571            )
572            .await
573            .expect("Failed to process sync");
574
575        // No m.room.name event, no heroes, no members => considered an empty room!
576        let client_room = client.get_room(room_id).expect("No room found");
577        assert!(client_room.name().is_none());
578        assert_eq!(
579            client_room.compute_display_name().await.unwrap().into_inner().to_string(),
580            "Empty Room"
581        );
582        assert_eq!(client_room.state(), RoomState::Joined);
583
584        // And it is added to the list of joined rooms only.
585        assert!(sync_resp.rooms.joined.contains_key(room_id));
586        assert!(!sync_resp.rooms.left.contains_key(room_id));
587        assert!(!sync_resp.rooms.invited.contains_key(room_id));
588        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
589    }
590
591    #[async_test]
592    async fn test_room_name_event() {
593        // Given a logged-in client
594        let client = logged_in_base_client(None).await;
595        let room_id = room_id!("!r:e.uk");
596
597        // When I send sliding sync response containing a room with a name set in the
598        // sliding sync response, and a m.room.name event,
599        let mut room = http::response::Room::new();
600
601        room.name = Some("little room".to_owned());
602        set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
603
604        let response = response_with_room(room_id, room);
605        client
606            .process_sliding_sync(
607                &response,
608                &RequestedRequiredStates::default(),
609                &client.state_store_lock().lock().await,
610            )
611            .await
612            .expect("Failed to process sync");
613
614        // The name is known.
615        let client_room = client.get_room(room_id).expect("No room found");
616        assert_eq!(client_room.name().as_deref(), Some("The Name"));
617        assert_eq!(
618            client_room.compute_display_name().await.unwrap().into_inner().to_string(),
619            "The Name"
620        );
621    }
622
623    #[async_test]
624    async fn test_missing_invited_room_name_event() {
625        // Given a logged-in client,
626        let client = logged_in_base_client(None).await;
627        let room_id = room_id!("!r:e.uk");
628        let user_id = user_id!("@w:e.uk");
629        let inviter = user_id!("@john:mastodon.org");
630
631        // When I send sliding sync response containing a room with a name set in the
632        // sliding sync response,
633        let mut room = http::response::Room::new();
634        set_room_invited(&mut room, inviter, user_id);
635        room.name = Some("name from sliding sync response".to_owned());
636        let response = response_with_room(room_id, room);
637        let sync_resp = client
638            .process_sliding_sync(
639                &response,
640                &RequestedRequiredStates::default(),
641                &client.state_store_lock().lock().await,
642            )
643            .await
644            .expect("Failed to process sync");
645
646        // Then the room doesn't have the name in the client.
647        let client_room = client.get_room(room_id).expect("No room found");
648        assert!(client_room.name().is_none());
649
650        // No m.room.name event, no heroes => using the invited member.
651        assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
652
653        assert_eq!(client_room.state(), RoomState::Invited);
654
655        // And it is added to the list of invited rooms only.
656        assert!(!sync_resp.rooms.joined.contains_key(room_id));
657        assert!(!sync_resp.rooms.left.contains_key(room_id));
658        assert!(sync_resp.rooms.invited.contains_key(room_id));
659        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
660    }
661
662    #[async_test]
663    async fn test_invited_room_name_event() {
664        // Given a logged-in client,
665        let client = logged_in_base_client(None).await;
666        let room_id = room_id!("!r:e.uk");
667        let user_id = user_id!("@w:e.uk");
668        let inviter = user_id!("@john:mastodon.org");
669
670        // When I send sliding sync response containing a room with a name set in the
671        // sliding sync response, and a m.room.name event,
672        let mut room = http::response::Room::new();
673
674        set_room_invited(&mut room, inviter, user_id);
675
676        room.name = Some("name from sliding sync response".to_owned());
677        set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
678
679        let response = response_with_room(room_id, room);
680        client
681            .process_sliding_sync(
682                &response,
683                &RequestedRequiredStates::default(),
684                &client.state_store_lock().lock().await,
685            )
686            .await
687            .expect("Failed to process sync");
688
689        // The name is known.
690        let client_room = client.get_room(room_id).expect("No room found");
691        assert_eq!(client_room.name().as_deref(), Some("The Name"));
692        assert_eq!(
693            client_room.compute_display_name().await.unwrap().into_inner().to_string(),
694            "The Name"
695        );
696    }
697
698    #[async_test]
699    async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
700        // Given a logged-in client,
701        let client = logged_in_base_client(None).await;
702        let room_id = room_id!("!r:e.uk");
703        let user_id = client.session_meta().unwrap().user_id.to_owned();
704
705        // When the room is properly set as knocked with the current user id as state
706        // key,
707        let mut room = http::response::Room::new();
708        set_room_knocked(&mut room, &user_id);
709
710        let response = response_with_room(room_id, room);
711        client
712            .process_sliding_sync(
713                &response,
714                &RequestedRequiredStates::default(),
715                &client.state_store_lock().lock().await,
716            )
717            .await
718            .expect("Failed to process sync");
719
720        // The room is knocked.
721        let client_room = client.get_room(room_id).expect("No room found");
722        assert_eq!(client_room.state(), RoomState::Knocked);
723    }
724
725    #[async_test]
726    async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
727     {
728        // Given a logged-in client,
729        let client = logged_in_base_client(None).await;
730        let room_id = room_id!("!r:e.uk");
731        let user_id = user_id!("@w:e.uk");
732
733        // When the room is set as knocked with a random user id as state key,
734        let mut room = http::response::Room::new();
735        set_room_knocked(&mut room, user_id);
736
737        let response = response_with_room(room_id, room);
738        client
739            .process_sliding_sync(
740                &response,
741                &RequestedRequiredStates::default(),
742                &client.state_store_lock().lock().await,
743            )
744            .await
745            .expect("Failed to process sync");
746
747        // The room is invited since the membership event doesn't belong to the current
748        // user.
749        let client_room = client.get_room(room_id).expect("No room found");
750        assert_eq!(client_room.state(), RoomState::Invited);
751    }
752
753    #[async_test]
754    async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
755     {
756        // Given a logged-in client,
757        let client = logged_in_base_client(None).await;
758        let room_id = room_id!("!r:e.uk");
759        let user_id = client.session_meta().unwrap().user_id.to_owned();
760
761        // When the room has the wrong membership state in its invite_state
762        let mut room = http::response::Room::new();
763        let event = Raw::new(&json!({
764            "type": "m.room.member",
765            "sender": user_id,
766            "content": {
767                "is_direct": true,
768                "membership": "join",
769            },
770            "state_key": user_id,
771        }))
772        .expect("Failed to make raw event")
773        .cast_unchecked();
774        room.invite_state = Some(vec![event]);
775
776        let response = response_with_room(room_id, room);
777        client
778            .process_sliding_sync(
779                &response,
780                &RequestedRequiredStates::default(),
781                &client.state_store_lock().lock().await,
782            )
783            .await
784            .expect("Failed to process sync");
785
786        // The room is marked as invited.
787        let client_room = client.get_room(room_id).expect("No room found");
788        assert_eq!(client_room.state(), RoomState::Invited);
789    }
790
791    #[async_test]
792    async fn test_left_a_room_from_required_state_event() {
793        // Given a logged-in client
794        let client = logged_in_base_client(None).await;
795        let room_id = room_id!("!r:e.uk");
796        let user_id = user_id!("@u:e.uk");
797
798        // When I join…
799        let mut room = http::response::Room::new();
800        set_room_joined(&mut room, user_id);
801        let response = response_with_room(room_id, room);
802        client
803            .process_sliding_sync(
804                &response,
805                &RequestedRequiredStates::default(),
806                &client.state_store_lock().lock().await,
807            )
808            .await
809            .expect("Failed to process sync");
810        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
811
812        // And then leave with a `required_state` state event…
813        let mut room = http::response::Room::new();
814        set_room_left(&mut room, user_id);
815        let response = response_with_room(room_id, room);
816        let sync_resp = client
817            .process_sliding_sync(
818                &response,
819                &RequestedRequiredStates::default(),
820                &client.state_store_lock().lock().await,
821            )
822            .await
823            .expect("Failed to process sync");
824
825        // The room is left.
826        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
827
828        // And it is added to the list of left rooms only.
829        assert!(!sync_resp.rooms.joined.contains_key(room_id));
830        assert!(sync_resp.rooms.left.contains_key(room_id));
831        assert!(!sync_resp.rooms.invited.contains_key(room_id));
832        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
833    }
834
835    #[async_test]
836    async fn test_kick_or_ban_updates_room_to_left() {
837        for membership in [MembershipState::Leave, MembershipState::Ban] {
838            let room_id = room_id!("!r:e.uk");
839            let user_a_id = user_id!("@a:e.uk");
840            let user_b_id = user_id!("@b:e.uk");
841            let client = logged_in_base_client(Some(user_a_id)).await;
842
843            // When I join…
844            let mut room = http::response::Room::new();
845            set_room_joined(&mut room, user_a_id);
846            let response = response_with_room(room_id, room);
847            client
848                .process_sliding_sync(
849                    &response,
850                    &RequestedRequiredStates::default(),
851                    &client.state_store_lock().lock().await,
852                )
853                .await
854                .expect("Failed to process sync");
855            assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
856
857            // And then get kicked/banned with a `required_state` state event…
858            let mut room = http::response::Room::new();
859            room.required_state.push(make_state_event(
860                user_b_id,
861                user_a_id.as_str(),
862                RoomMemberEventContent::new(membership.clone()),
863                None,
864            ));
865            let response = response_with_room(room_id, room);
866            let sync_resp = client
867                .process_sliding_sync(
868                    &response,
869                    &RequestedRequiredStates::default(),
870                    &client.state_store_lock().lock().await,
871                )
872                .await
873                .expect("Failed to process sync");
874
875            match membership {
876                MembershipState::Leave => {
877                    // The room is left.
878                    assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
879                }
880                MembershipState::Ban => {
881                    // The room is banned.
882                    assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
883                }
884                _ => panic!("Unexpected membership state found: {membership}"),
885            }
886
887            // And it is added to the list of left rooms only.
888            assert!(!sync_resp.rooms.joined.contains_key(room_id));
889            assert!(sync_resp.rooms.left.contains_key(room_id));
890            assert!(!sync_resp.rooms.invited.contains_key(room_id));
891            assert!(!sync_resp.rooms.knocked.contains_key(room_id));
892        }
893    }
894
895    #[async_test]
896    async fn test_left_a_room_from_timeline_state_event() {
897        // Given a logged-in client
898        let client = logged_in_base_client(None).await;
899        let room_id = room_id!("!r:e.uk");
900        let user_id = user_id!("@u:e.uk");
901
902        // When I join…
903        let mut room = http::response::Room::new();
904        set_room_joined(&mut room, user_id);
905        let response = response_with_room(room_id, room);
906        client
907            .process_sliding_sync(
908                &response,
909                &RequestedRequiredStates::default(),
910                &client.state_store_lock().lock().await,
911            )
912            .await
913            .expect("Failed to process sync");
914        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
915
916        // And then leave with a `timeline` state event…
917        let mut room = http::response::Room::new();
918        set_room_left_as_timeline_event(&mut room, user_id);
919        let response = response_with_room(room_id, room);
920        client
921            .process_sliding_sync(
922                &response,
923                &RequestedRequiredStates::default(),
924                &client.state_store_lock().lock().await,
925            )
926            .await
927            .expect("Failed to process sync");
928
929        // The room is NOT left because state events from `timeline` must be IGNORED!
930        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
931    }
932
933    #[async_test]
934    async fn test_can_be_reinvited_to_a_left_room() {
935        // See https://github.com/matrix-org/matrix-rust-sdk/issues/1834
936
937        // Given a logged-in client
938        let client = logged_in_base_client(None).await;
939        let room_id = room_id!("!r:e.uk");
940        let user_id = user_id!("@u:e.uk");
941
942        // When I join...
943        let mut room = http::response::Room::new();
944        set_room_joined(&mut room, user_id);
945        let response = response_with_room(room_id, room);
946        client
947            .process_sliding_sync(
948                &response,
949                &RequestedRequiredStates::default(),
950                &client.state_store_lock().lock().await,
951            )
952            .await
953            .expect("Failed to process sync");
954        // (sanity: state is join)
955        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
956
957        // And then leave...
958        let mut room = http::response::Room::new();
959        set_room_left(&mut room, user_id);
960        let response = response_with_room(room_id, room);
961        client
962            .process_sliding_sync(
963                &response,
964                &RequestedRequiredStates::default(),
965                &client.state_store_lock().lock().await,
966            )
967            .await
968            .expect("Failed to process sync");
969        // (sanity: state is left)
970        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
971
972        // And then get invited back
973        let mut room = http::response::Room::new();
974        set_room_invited(&mut room, user_id, user_id);
975        let response = response_with_room(room_id, room);
976        client
977            .process_sliding_sync(
978                &response,
979                &RequestedRequiredStates::default(),
980                &client.state_store_lock().lock().await,
981            )
982            .await
983            .expect("Failed to process sync");
984
985        // Then the room is in the invite state
986        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
987    }
988
989    #[async_test]
990    async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
991        let room_id = room_id!("!r:e.uk");
992        let user_a_id = user_id!("@a:e.uk");
993        let user_b_id = user_id!("@b:e.uk");
994
995        // Given we have a DM with B, who is joined
996        let client = logged_in_base_client(None).await;
997        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
998
999        // (Sanity: B is a direct target, and is in Join state)
1000        assert!(
1001            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1002        );
1003        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1004
1005        // When B leaves
1006        update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1007
1008        // Then B is still a direct target, and is in Leave state (B is a direct target
1009        // because we want to return to our old DM in the UI even if the other
1010        // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
1011        assert!(
1012            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1013        );
1014        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1015    }
1016
1017    #[async_test]
1018    async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
1019     {
1020        let room_id = room_id!("!r:e.uk");
1021        let user_a_id = user_id!("@a:e.uk");
1022        let user_b_id = user_id!("@b:e.uk");
1023
1024        // Given I have invited B to a DM
1025        let client = logged_in_base_client(None).await;
1026        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1027
1028        // (Sanity: B is a direct target, and is in Invite state)
1029        assert!(
1030            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1031        );
1032        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1033
1034        // When B declines the invitation (i.e. leaves)
1035        update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1036
1037        // Then B is still a direct target, and is in Leave state (B is a direct target
1038        // because we want to return to our old DM in the UI even if the other
1039        // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
1040        assert!(
1041            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1042        );
1043        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1044    }
1045
1046    #[async_test]
1047    async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1048        let room_id = room_id!("!r:bar.org");
1049        let user_a_id = user_id!("@a:bar.org");
1050        let user_b_id = user_id!("@b:bar.org");
1051
1052        // Given we have a DM with B, who is joined
1053        let client = logged_in_base_client(None).await;
1054        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1055
1056        // (Sanity: A is in Join state)
1057        assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1058
1059        // (Sanity: B is a direct target, and is in Join state)
1060        assert!(
1061            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1062        );
1063        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1064
1065        let room = client.get_room(room_id).unwrap();
1066
1067        assert_eq!(room.active_members_count(), 2);
1068        assert_eq!(room.joined_members_count(), 2);
1069        assert_eq!(room.invited_members_count(), 0);
1070    }
1071
1072    #[async_test]
1073    async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1074        let room_id = room_id!("!r:bar.org");
1075        let user_a_id = user_id!("@a:bar.org");
1076        let user_b_id = user_id!("@b:bar.org");
1077
1078        // Given we have a DM with B, who is joined
1079        let client = logged_in_base_client(None).await;
1080        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1081
1082        // (Sanity: A is in Join state)
1083        assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1084
1085        // (Sanity: B is a direct target, and is in Join state)
1086        assert!(
1087            direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1088        );
1089        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1090
1091        let room = client.get_room(room_id).unwrap();
1092
1093        assert_eq!(room.active_members_count(), 2);
1094        assert_eq!(room.joined_members_count(), 1);
1095        assert_eq!(room.invited_members_count(), 1);
1096    }
1097
1098    #[async_test]
1099    async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1100        // Given a logged-in client
1101        let client = logged_in_base_client(None).await;
1102        let room_id = room_id!("!r:e.uk");
1103
1104        // When I send sliding sync response containing a room with an avatar
1105        let room = {
1106            let mut room = http::response::Room::new();
1107            room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1108
1109            room
1110        };
1111        let response = response_with_room(room_id, room);
1112        client
1113            .process_sliding_sync(
1114                &response,
1115                &RequestedRequiredStates::default(),
1116                &client.state_store_lock().lock().await,
1117            )
1118            .await
1119            .expect("Failed to process sync");
1120
1121        // Then the room in the client has the avatar
1122        let client_room = client.get_room(room_id).expect("No room found");
1123        assert_eq!(
1124            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1125            "med1"
1126        );
1127    }
1128
1129    #[async_test]
1130    async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1131        // Given a logged-in client
1132        let client = logged_in_base_client(None).await;
1133        let room_id = room_id!("!r:e.uk");
1134
1135        // Set the avatar.
1136
1137        // When I send sliding sync response containing a room with an avatar
1138        let room = {
1139            let mut room = http::response::Room::new();
1140            room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1141
1142            room
1143        };
1144        let response = response_with_room(room_id, room);
1145        client
1146            .process_sliding_sync(
1147                &response,
1148                &RequestedRequiredStates::default(),
1149                &client.state_store_lock().lock().await,
1150            )
1151            .await
1152            .expect("Failed to process sync");
1153
1154        // Then the room in the client has the avatar
1155        let client_room = client.get_room(room_id).expect("No room found");
1156        assert_eq!(
1157            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1158            "med1"
1159        );
1160
1161        // No avatar. Still here.
1162
1163        // When I send sliding sync response containing no avatar.
1164        let room = http::response::Room::new();
1165        let response = response_with_room(room_id, room);
1166        client
1167            .process_sliding_sync(
1168                &response,
1169                &RequestedRequiredStates::default(),
1170                &client.state_store_lock().lock().await,
1171            )
1172            .await
1173            .expect("Failed to process sync");
1174
1175        // Then the room in the client still has the avatar
1176        let client_room = client.get_room(room_id).expect("No room found");
1177        assert_eq!(
1178            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1179            "med1"
1180        );
1181
1182        // Avatar is unset.
1183
1184        // When I send sliding sync response containing an avatar set to `null` (!).
1185        let room = {
1186            let mut room = http::response::Room::new();
1187            room.avatar = JsOption::Null;
1188
1189            room
1190        };
1191        let response = response_with_room(room_id, room);
1192        client
1193            .process_sliding_sync(
1194                &response,
1195                &RequestedRequiredStates::default(),
1196                &client.state_store_lock().lock().await,
1197            )
1198            .await
1199            .expect("Failed to process sync");
1200
1201        // Then the room in the client has no more avatar
1202        let client_room = client.get_room(room_id).expect("No room found");
1203        assert!(client_room.avatar_url().is_none());
1204    }
1205
1206    #[async_test]
1207    async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1208        // Given a logged-in client
1209        let client = logged_in_base_client(None).await;
1210        let room_id = room_id!("!r:e.uk");
1211        let user_id = user_id!("@u:e.uk");
1212
1213        // When I send sliding sync response containing a room with an avatar
1214        let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1215        let response = response_with_room(room_id, room);
1216        client
1217            .process_sliding_sync(
1218                &response,
1219                &RequestedRequiredStates::default(),
1220                &client.state_store_lock().lock().await,
1221            )
1222            .await
1223            .expect("Failed to process sync");
1224
1225        // Then the room in the client has the avatar
1226        let client_room = client.get_room(room_id).expect("No room found");
1227        assert_eq!(
1228            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1229            "med1"
1230        );
1231    }
1232
1233    #[async_test]
1234    async fn test_invitation_room_is_added_to_client_and_invite_list() {
1235        // Given a logged-in client
1236        let client = logged_in_base_client(None).await;
1237        let room_id = room_id!("!r:e.uk");
1238        let user_id = user_id!("@u:e.uk");
1239
1240        let mut room_info_notable_update = client.room_info_notable_update_receiver();
1241
1242        // When I send sliding sync response containing an invited room
1243        let mut room = http::response::Room::new();
1244        set_room_invited(&mut room, user_id, user_id);
1245        let response = response_with_room(room_id, room);
1246        let sync_resp = client
1247            .process_sliding_sync(
1248                &response,
1249                &RequestedRequiredStates::default(),
1250                &client.state_store_lock().lock().await,
1251            )
1252            .await
1253            .expect("Failed to process sync");
1254
1255        // Then the room is added to the client
1256        let client_room = client.get_room(room_id).expect("No room found");
1257        assert_eq!(client_room.room_id(), room_id);
1258        assert_eq!(client_room.state(), RoomState::Invited);
1259
1260        // And it is added to the list of invited rooms, not the joined ones
1261        assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1262        assert!(!sync_resp.rooms.joined.contains_key(room_id));
1263
1264        assert_matches!(
1265            room_info_notable_update.recv().await,
1266            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1267                assert_eq!(received_room_id, room_id);
1268                // The reason we are looking for :-].
1269                assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1270            }
1271        );
1272        assert_matches!(
1273            room_info_notable_update.recv().await,
1274            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1275                assert_eq!(received_room_id, room_id);
1276                // The reason we are looking for :-].
1277                assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1278            }
1279        );
1280        assert!(room_info_notable_update.is_empty());
1281    }
1282
1283    #[async_test]
1284    async fn test_knock_room_is_added_to_client_and_knock_list() {
1285        // Given a logged-in client
1286        let client = logged_in_base_client(None).await;
1287        let room_id = room_id!("!r:e.uk");
1288        let user_id = user_id!("@u:e.uk");
1289
1290        let mut room_info_notable_update = client.room_info_notable_update_receiver();
1291
1292        // When I send sliding sync response containing an invited room
1293        let mut room = http::response::Room::new();
1294        set_room_knocked(&mut room, user_id);
1295        let response = response_with_room(room_id, room);
1296        let sync_resp = client
1297            .process_sliding_sync(
1298                &response,
1299                &RequestedRequiredStates::default(),
1300                &client.state_store_lock().lock().await,
1301            )
1302            .await
1303            .expect("Failed to process sync");
1304
1305        // Then the room is added to the client
1306        let client_room = client.get_room(room_id).expect("No room found");
1307        assert_eq!(client_room.room_id(), room_id);
1308        assert_eq!(client_room.state(), RoomState::Knocked);
1309
1310        // And it is added to the list of invited rooms, not the joined ones
1311        assert!(!sync_resp.rooms.knocked[room_id].knock_state.is_empty());
1312        assert!(!sync_resp.rooms.joined.contains_key(room_id));
1313
1314        assert_matches!(
1315            room_info_notable_update.recv().await,
1316            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1317                assert_eq!(received_room_id, room_id);
1318                // The reason we are looking for :-].
1319                assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1320            }
1321        );
1322        assert_matches!(
1323            room_info_notable_update.recv().await,
1324            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1325                assert_eq!(received_room_id, room_id);
1326                // The reason we are looking for :-].
1327                assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1328            }
1329        );
1330        assert!(room_info_notable_update.is_empty());
1331    }
1332
1333    #[async_test]
1334    async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1335        // Given a logged-in client
1336        let client = logged_in_base_client(None).await;
1337        let room_id = room_id!("!r:e.uk");
1338        let user_id = user_id!("@u:e.uk");
1339
1340        // When I send sliding sync response containing an invited room with an avatar
1341        let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1342        set_room_invited(&mut room, user_id, user_id);
1343        let response = response_with_room(room_id, room);
1344        client
1345            .process_sliding_sync(
1346                &response,
1347                &RequestedRequiredStates::default(),
1348                &client.state_store_lock().lock().await,
1349            )
1350            .await
1351            .expect("Failed to process sync");
1352
1353        // Then the room in the client has the avatar
1354        let client_room = client.get_room(room_id).expect("No room found");
1355        assert_eq!(
1356            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1357            "med1"
1358        );
1359    }
1360
1361    #[async_test]
1362    async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1363     {
1364        // Given a logged-in client
1365        let client = logged_in_base_client(None).await;
1366        let room_id = room_id!("!r:e.uk");
1367        let user_id = user_id!("@u:e.uk");
1368        let room_alias_id = room_alias_id!("#myroom:e.uk");
1369
1370        // When I send sliding sync response containing an invited room with an avatar
1371        let mut room = room_with_canonical_alias(room_alias_id, user_id);
1372        set_room_invited(&mut room, user_id, user_id);
1373        let response = response_with_room(room_id, room);
1374        client
1375            .process_sliding_sync(
1376                &response,
1377                &RequestedRequiredStates::default(),
1378                &client.state_store_lock().lock().await,
1379            )
1380            .await
1381            .expect("Failed to process sync");
1382
1383        // Then the room in the client has the avatar
1384        let client_room = client.get_room(room_id).expect("No room found");
1385        assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1386    }
1387
1388    #[async_test]
1389    async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1390        // Given a logged-in client
1391        let client = logged_in_base_client(None).await;
1392        let room_id = room_id!("!r:e.uk");
1393        let user_id = user_id!("@u:e.uk");
1394        let room_alias_id = room_alias_id!("#myroom:e.uk");
1395
1396        // When the sliding sync response contains an explicit room name as well as an
1397        // alias
1398        let mut room = room_with_canonical_alias(room_alias_id, user_id);
1399        room.name = Some("This came from the server".to_owned());
1400        let response = response_with_room(room_id, room);
1401        client
1402            .process_sliding_sync(
1403                &response,
1404                &RequestedRequiredStates::default(),
1405                &client.state_store_lock().lock().await,
1406            )
1407            .await
1408            .expect("Failed to process sync");
1409
1410        // Then the room's name is NOT overridden by the server-computed display name.
1411        let client_room = client.get_room(room_id).expect("No room found");
1412        assert_eq!(
1413            client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1414            "myroom"
1415        );
1416        assert!(client_room.name().is_none());
1417    }
1418
1419    #[async_test]
1420    async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1421        let client = logged_in_base_client(None).await;
1422        let user_id = user_id!("@u:e.uk");
1423        let room_id = room_id!("!r:e.uk");
1424
1425        let mut room_info_notable_update = client.room_info_notable_update_receiver();
1426
1427        let room = room_with_name("Hello World", user_id);
1428        let response = response_with_room(room_id, room);
1429        client
1430            .process_sliding_sync(
1431                &response,
1432                &RequestedRequiredStates::default(),
1433                &client.state_store_lock().lock().await,
1434            )
1435            .await
1436            .expect("Failed to process sync");
1437
1438        let room = client.get_room(room_id).expect("No room found");
1439        assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1440
1441        assert_matches!(
1442            room_info_notable_update.recv().await,
1443            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1444                assert_eq!(received_room_id, room_id);
1445                assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1446            }
1447        );
1448        assert_matches!(
1449            room_info_notable_update.recv().await,
1450            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1451                assert_eq!(received_room_id, room_id);
1452                // The reason we are looking for :-].
1453                assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1454            }
1455        );
1456        assert!(room_info_notable_update.is_empty());
1457    }
1458
1459    #[async_test]
1460    async fn test_display_name_is_persisted_from_sliding_sync() {
1461        let user_id = user_id!("@u:e.uk");
1462        let room_id = room_id!("!r:e.uk");
1463        let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1464        let state_store;
1465
1466        {
1467            let client = {
1468                let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1469                state_store = store.state_store.clone();
1470
1471                let client =
1472                    BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1473                client
1474                    .activate(
1475                        session_meta.clone(),
1476                        RoomLoadSettings::default(),
1477                        #[cfg(feature = "e2e-encryption")]
1478                        None,
1479                    )
1480                    .await
1481                    .expect("`activate` failed!");
1482
1483                client
1484            };
1485
1486            // When the sliding sync response contains an explicit room name as well as an
1487            // alias
1488            let room = room_with_name("Hello World", user_id);
1489            let response = response_with_room(room_id, room);
1490            client
1491                .process_sliding_sync(
1492                    &response,
1493                    &RequestedRequiredStates::default(),
1494                    &client.state_store_lock().lock().await,
1495                )
1496                .await
1497                .expect("Failed to process sync");
1498
1499            let room = client.get_room(room_id).expect("No room found");
1500            assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1501        }
1502
1503        {
1504            let client = {
1505                let mut store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1506                store.state_store = state_store;
1507                let client =
1508                    BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1509                client
1510                    .activate(
1511                        session_meta,
1512                        RoomLoadSettings::default(),
1513                        #[cfg(feature = "e2e-encryption")]
1514                        None,
1515                    )
1516                    .await
1517                    .expect("`activate` failed!");
1518
1519                client
1520            };
1521
1522            let room = client.get_room(room_id).expect("No room found");
1523            assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1524        }
1525    }
1526
1527    #[async_test]
1528    async fn test_compute_heroes_from_sliding_sync() {
1529        // Given a logged-in client
1530        let client = logged_in_base_client(None).await;
1531        let room_id = room_id!("!r:e.uk");
1532        let gordon = owned_user_id!("@gordon:e.uk");
1533        let alice = owned_user_id!("@alice:e.uk");
1534
1535        // When I send sliding sync response containing a room (with identifiable data
1536        // in `heroes`)
1537        let mut room = http::response::Room::new();
1538        room.heroes = Some(vec![
1539            assign!(http::response::Hero::new(gordon), {
1540                name: Some("Gordon".to_owned()),
1541            }),
1542            assign!(http::response::Hero::new(alice), {
1543                name: Some("Alice".to_owned()),
1544                avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1545            }),
1546        ]);
1547        let response = response_with_room(room_id, room);
1548        let _sync_resp = client
1549            .process_sliding_sync(
1550                &response,
1551                &RequestedRequiredStates::default(),
1552                &client.state_store_lock().lock().await,
1553            )
1554            .await
1555            .expect("Failed to process sync");
1556
1557        // Then the room appears in the client.
1558        let client_room = client.get_room(room_id).expect("No room found");
1559        assert_eq!(client_room.room_id(), room_id);
1560        assert_eq!(client_room.state(), RoomState::Joined);
1561
1562        // And heroes are part of the summary.
1563        assert_eq!(
1564            client_room.clone_info().summary.heroes(),
1565            &[
1566                RoomHero {
1567                    user_id: owned_user_id!("@gordon:e.uk"),
1568                    display_name: Some("Gordon".to_owned()),
1569                    avatar_url: None
1570                },
1571                RoomHero {
1572                    user_id: owned_user_id!("@alice:e.uk"),
1573                    display_name: Some("Alice".to_owned()),
1574                    avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1575                },
1576            ]
1577        );
1578    }
1579
1580    #[async_test]
1581    async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1582        // Given a logged-in client
1583        let client = logged_in_base_client(None).await;
1584        let room_id = room_id!("!r:e.uk");
1585
1586        // When I send sliding sync response containing a room with a recency stamp
1587        let room = assign!(http::response::Room::new(), {
1588            bump_stamp: Some(42u32.into()),
1589        });
1590        let response = response_with_room(room_id, room);
1591        client
1592            .process_sliding_sync(
1593                &response,
1594                &RequestedRequiredStates::default(),
1595                &client.state_store_lock().lock().await,
1596            )
1597            .await
1598            .expect("Failed to process sync");
1599
1600        // Then the room in the client has the recency stamp
1601        let client_room = client.get_room(room_id).expect("No room found");
1602        assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1603    }
1604
1605    #[async_test]
1606    async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1607        // Given a logged-in client
1608        let client = logged_in_base_client(None).await;
1609        let room_id = room_id!("!r:e.uk");
1610
1611        {
1612            // When I send sliding sync response containing a room with a recency stamp
1613            let room = assign!(http::response::Room::new(), {
1614                bump_stamp: Some(42u32.into()),
1615            });
1616            let response = response_with_room(room_id, room);
1617            client
1618                .process_sliding_sync(
1619                    &response,
1620                    &RequestedRequiredStates::default(),
1621                    &client.state_store_lock().lock().await,
1622                )
1623                .await
1624                .expect("Failed to process sync");
1625
1626            // Then the room in the client has the recency stamp
1627            let client_room = client.get_room(room_id).expect("No room found");
1628            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1629        }
1630
1631        {
1632            // When I send sliding sync response containing a room with NO recency stamp
1633            let room = assign!(http::response::Room::new(), {
1634                bump_stamp: None,
1635            });
1636            let response = response_with_room(room_id, room);
1637            client
1638                .process_sliding_sync(
1639                    &response,
1640                    &RequestedRequiredStates::default(),
1641                    &client.state_store_lock().lock().await,
1642                )
1643                .await
1644                .expect("Failed to process sync");
1645
1646            // Then the room in the client has the previous recency stamp
1647            let client_room = client.get_room(room_id).expect("No room found");
1648            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1649        }
1650
1651        {
1652            // When I send sliding sync response containing a room with a NEW recency
1653            // timestamp
1654            let room = assign!(http::response::Room::new(), {
1655                bump_stamp: Some(153u32.into()),
1656            });
1657            let response = response_with_room(room_id, room);
1658            client
1659                .process_sliding_sync(
1660                    &response,
1661                    &RequestedRequiredStates::default(),
1662                    &client.state_store_lock().lock().await,
1663                )
1664                .await
1665                .expect("Failed to process sync");
1666
1667            // Then the room in the client has the recency stamp
1668            let client_room = client.get_room(room_id).expect("No room found");
1669            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1670        }
1671    }
1672
1673    #[async_test]
1674    async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1675        // Given a logged-in client
1676        let client = logged_in_base_client(None).await;
1677        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1678        let room_id = room_id!("!r:e.uk");
1679
1680        // When I send sliding sync response containing a room with a recency stamp.
1681        let room = assign!(http::response::Room::new(), {
1682            bump_stamp: Some(42u32.into()),
1683        });
1684        let response = response_with_room(room_id, room);
1685        client
1686            .process_sliding_sync(
1687                &response,
1688                &RequestedRequiredStates::default(),
1689                &client.state_store_lock().lock().await,
1690            )
1691            .await
1692            .expect("Failed to process sync");
1693
1694        // Then a room info notable update is NOT received, because it's the first time
1695        // the room is seen.
1696        assert_matches!(
1697            room_info_notable_update_stream.recv().await,
1698            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1699                assert_eq!(received_room_id, room_id);
1700                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1701            }
1702        );
1703        assert_matches!(
1704            room_info_notable_update_stream.recv().await,
1705            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1706                assert_eq!(received_room_id, room_id);
1707                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1708            }
1709        );
1710        assert!(room_info_notable_update_stream.is_empty());
1711
1712        // When I send sliding sync response containing a room with a recency stamp.
1713        let room = assign!(http::response::Room::new(), {
1714            bump_stamp: Some(43u32.into()),
1715        });
1716        let response = response_with_room(room_id, room);
1717        client
1718            .process_sliding_sync(
1719                &response,
1720                &RequestedRequiredStates::default(),
1721                &client.state_store_lock().lock().await,
1722            )
1723            .await
1724            .expect("Failed to process sync");
1725
1726        // Then a room info notable update is received.
1727        assert_matches!(
1728            room_info_notable_update_stream.recv().await,
1729            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1730                assert_eq!(received_room_id, room_id);
1731                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1732            }
1733        );
1734        assert!(room_info_notable_update_stream.is_empty());
1735    }
1736
1737    #[async_test]
1738    async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1739        // Given a logged-in client
1740        let client = logged_in_base_client(None).await;
1741        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1742
1743        // When I send sliding sync response containing a new room.
1744        let room_id = room_id!("!r:e.uk");
1745        let room = http::response::Room::new();
1746        let response = response_with_room(room_id, room);
1747        client
1748            .process_sliding_sync(
1749                &response,
1750                &RequestedRequiredStates::default(),
1751                &client.state_store_lock().lock().await,
1752            )
1753            .await
1754            .expect("Failed to process sync");
1755
1756        // Other notable update reason. We don't really care about them here.
1757        assert_matches!(
1758            room_info_notable_update_stream.recv().await,
1759            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1760                assert_eq!(received_room_id, room_id);
1761                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1762            }
1763        );
1764        assert_matches!(
1765            room_info_notable_update_stream.recv().await,
1766            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1767                assert_eq!(received_room_id, room_id);
1768                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1769            }
1770        );
1771
1772        // Send sliding sync response containing a membership event with 'join' value.
1773        let room_id = room_id!("!r:e.uk");
1774        let events = vec![
1775            Raw::from_json_string(
1776                json!({
1777                    "type": "m.room.member",
1778                    "event_id": "$3",
1779                    "content": { "membership": "join" },
1780                    "sender": "@u:h.uk",
1781                    "origin_server_ts": 12344445,
1782                    "state_key": "@u:e.uk",
1783                })
1784                .to_string(),
1785            )
1786            .unwrap(),
1787        ];
1788        let room = assign!(http::response::Room::new(), {
1789            required_state: events,
1790        });
1791        let response = response_with_room(room_id, room);
1792        client
1793            .process_sliding_sync(
1794                &response,
1795                &RequestedRequiredStates::default(),
1796                &client.state_store_lock().lock().await,
1797            )
1798            .await
1799            .expect("Failed to process sync");
1800
1801        // Room was already joined, no `MEMBERSHIP` update should be triggered here
1802        assert_matches!(
1803            room_info_notable_update_stream.recv().await,
1804            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1805                assert_eq!(received_room_id, room_id);
1806                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1807            }
1808        );
1809        assert!(room_info_notable_update_stream.is_empty());
1810
1811        let events = vec![
1812            Raw::from_json_string(
1813                json!({
1814                    "type": "m.room.member",
1815                    "event_id": "$3",
1816                    "content": { "membership": "leave" },
1817                    "sender": "@u:h.uk",
1818                    "origin_server_ts": 12344445,
1819                    "state_key": "@u:e.uk",
1820                })
1821                .to_string(),
1822            )
1823            .unwrap(),
1824        ];
1825        let room = assign!(http::response::Room::new(), {
1826            required_state: events,
1827        });
1828        let response = response_with_room(room_id, room);
1829        client
1830            .process_sliding_sync(
1831                &response,
1832                &RequestedRequiredStates::default(),
1833                &client.state_store_lock().lock().await,
1834            )
1835            .await
1836            .expect("Failed to process sync");
1837
1838        // Then a room info notable update is received.
1839        assert_matches!(
1840            room_info_notable_update_stream.recv().await,
1841            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1842                assert_eq!(received_room_id, room_id);
1843                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1844            }
1845        );
1846        assert!(room_info_notable_update_stream.is_empty());
1847    }
1848
1849    #[async_test]
1850    async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1851        // Given a logged-in client,
1852        let client = logged_in_base_client(None).await;
1853        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1854
1855        // When I receive a sliding sync response containing a new room,
1856        let room_id = room_id!("!r:e.uk");
1857        let room = http::response::Room::new();
1858        let response = response_with_room(room_id, room);
1859        client
1860            .process_sliding_sync(
1861                &response,
1862                &RequestedRequiredStates::default(),
1863                &client.state_store_lock().lock().await,
1864            )
1865            .await
1866            .expect("Failed to process sync");
1867
1868        // Other notable updates are received, but not the ones we are interested by.
1869        assert_matches!(
1870            room_info_notable_update_stream.recv().await,
1871            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1872                assert_eq!(received_room_id, room_id);
1873                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1874            }
1875        );
1876        assert_matches!(
1877            room_info_notable_update_stream.recv().await,
1878            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1879                assert_eq!(received_room_id, room_id);
1880                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
1881            }
1882        );
1883        assert!(room_info_notable_update_stream.is_empty());
1884
1885        // When I receive a sliding sync response containing one update about an unread
1886        // marker,
1887        let room_id = room_id!("!r:e.uk");
1888        let room_account_data_events = vec![
1889            Raw::from_json_string(
1890                json!({
1891                    "type": "m.marked_unread",
1892                    "event_id": "$1",
1893                    "content": { "unread": true },
1894                    "sender": client.session_meta().unwrap().user_id,
1895                    "origin_server_ts": 12344445,
1896                })
1897                .to_string(),
1898            )
1899            .unwrap(),
1900        ];
1901        let mut response = response_with_room(room_id, http::response::Room::new());
1902        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1903
1904        client
1905            .process_sliding_sync(
1906                &response,
1907                &RequestedRequiredStates::default(),
1908                &client.state_store_lock().lock().await,
1909            )
1910            .await
1911            .expect("Failed to process sync");
1912
1913        // Then a room info notable update is received.
1914        assert_matches!(
1915            room_info_notable_update_stream.recv().await,
1916            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1917                assert_eq!(received_room_id, room_id);
1918                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
1919            }
1920        );
1921
1922        // But getting it again won't trigger a new notable update…
1923        client
1924            .process_sliding_sync(
1925                &response,
1926                &RequestedRequiredStates::default(),
1927                &client.state_store_lock().lock().await,
1928            )
1929            .await
1930            .expect("Failed to process sync");
1931
1932        assert_matches!(
1933            room_info_notable_update_stream.recv().await,
1934            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1935                assert_eq!(received_room_id, room_id);
1936                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1937            }
1938        );
1939        assert!(room_info_notable_update_stream.is_empty());
1940
1941        // …Unless its value changes!
1942        let room_account_data_events = vec![
1943            Raw::from_json_string(
1944                json!({
1945                    "type": "m.marked_unread",
1946                    "event_id": "$1",
1947                    "content": { "unread": false },
1948                    "sender": client.session_meta().unwrap().user_id,
1949                    "origin_server_ts": 12344445,
1950                })
1951                .to_string(),
1952            )
1953            .unwrap(),
1954        ];
1955        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1956        client
1957            .process_sliding_sync(
1958                &response,
1959                &RequestedRequiredStates::default(),
1960                &client.state_store_lock().lock().await,
1961            )
1962            .await
1963            .expect("Failed to process sync");
1964
1965        assert_matches!(
1966            room_info_notable_update_stream.recv().await,
1967            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1968                assert_eq!(received_room_id, room_id);
1969                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
1970            }
1971        );
1972        assert!(room_info_notable_update_stream.is_empty());
1973    }
1974
1975    #[async_test]
1976    async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
1977        // Given a logged-in client,
1978        let client = logged_in_base_client(None).await;
1979        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1980
1981        // When I receive a sliding sync response containing a new room,
1982        let room_id = room_id!("!r:e.uk");
1983        let room = http::response::Room::new();
1984        let response = response_with_room(room_id, room);
1985        client
1986            .process_sliding_sync(
1987                &response,
1988                &RequestedRequiredStates::default(),
1989                &client.state_store_lock().lock().await,
1990            )
1991            .await
1992            .expect("Failed to process sync");
1993
1994        // Other notable updates are received, but not the ones we are interested by.
1995        assert_matches!(
1996            room_info_notable_update_stream.recv().await,
1997            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1998                assert_eq!(received_room_id, room_id);
1999                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2000            }
2001        );
2002        assert_matches!(
2003            room_info_notable_update_stream.recv().await,
2004            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2005                assert_eq!(received_room_id, room_id);
2006                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2007            }
2008        );
2009        assert!(room_info_notable_update_stream.is_empty());
2010
2011        // When I receive a sliding sync response containing an `m.fully_read`
2012        // account-data event,
2013        let room_account_data_events = vec![
2014            Raw::from_json_string(
2015                json!({
2016                    "type": "m.fully_read",
2017                    "content": { "event_id": "$first" },
2018                })
2019                .to_string(),
2020            )
2021            .unwrap(),
2022        ];
2023        let mut response = response_with_room(room_id, http::response::Room::new());
2024        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2025
2026        client
2027            .process_sliding_sync(
2028                &response,
2029                &RequestedRequiredStates::default(),
2030                &client.state_store_lock().lock().await,
2031            )
2032            .await
2033            .expect("Failed to process sync");
2034
2035        // Then a `FULLY_READ` notable update is received,
2036        assert_matches!(
2037            room_info_notable_update_stream.recv().await,
2038            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2039                assert_eq!(received_room_id, room_id);
2040                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2041            }
2042        );
2043
2044        let room = client.get_room(room_id).expect("room should exist");
2045        assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2046
2047        // But getting the same value again won't trigger a new notable update…
2048        client
2049            .process_sliding_sync(
2050                &response,
2051                &RequestedRequiredStates::default(),
2052                &client.state_store_lock().lock().await,
2053            )
2054            .await
2055            .expect("Failed to process sync");
2056
2057        assert_matches!(
2058            room_info_notable_update_stream.recv().await,
2059            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2060                assert_eq!(received_room_id, room_id);
2061                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2062            }
2063        );
2064        assert!(room_info_notable_update_stream.is_empty());
2065
2066        // … Unless the event ID changes!
2067        let room_account_data_events = vec![
2068            Raw::from_json_string(
2069                json!({
2070                    "type": "m.fully_read",
2071                    "content": { "event_id": "$second" },
2072                })
2073                .to_string(),
2074            )
2075            .unwrap(),
2076        ];
2077        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2078        client
2079            .process_sliding_sync(
2080                &response,
2081                &RequestedRequiredStates::default(),
2082                &client.state_store_lock().lock().await,
2083            )
2084            .await
2085            .expect("Failed to process sync");
2086
2087        assert_matches!(
2088            room_info_notable_update_stream.recv().await,
2089            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2090                assert_eq!(received_room_id, room_id);
2091                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2092            }
2093        );
2094        assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2095        assert!(room_info_notable_update_stream.is_empty());
2096    }
2097
2098    #[async_test]
2099    async fn test_unstable_unread_marker_is_ignored_after_stable() {
2100        // Given a logged-in client,
2101        let client = logged_in_base_client(None).await;
2102        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2103
2104        // When I receive a sliding sync response containing a new room,
2105        let room_id = room_id!("!r:e.uk");
2106        let room = http::response::Room::new();
2107        let response = response_with_room(room_id, room);
2108        client
2109            .process_sliding_sync(
2110                &response,
2111                &RequestedRequiredStates::default(),
2112                &client.state_store_lock().lock().await,
2113            )
2114            .await
2115            .expect("Failed to process sync");
2116
2117        // Other notable updates are received, but not the ones we are interested by.
2118        assert_matches!(
2119            room_info_notable_update_stream.recv().await,
2120            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2121                assert_eq!(received_room_id, room_id);
2122                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2123            }
2124        );
2125        assert_matches!(
2126            room_info_notable_update_stream.recv().await,
2127            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2128                assert_eq!(received_room_id, room_id);
2129                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2130            }
2131        );
2132        assert!(room_info_notable_update_stream.is_empty());
2133
2134        // When I receive a sliding sync response containing one update about an
2135        // unstable unread marker,
2136        let room_id = room_id!("!r:e.uk");
2137        let unstable_room_account_data_events = vec![
2138            Raw::from_json_string(
2139                json!({
2140                    "type": "com.famedly.marked_unread",
2141                    "event_id": "$1",
2142                    "content": { "unread": true },
2143                    "sender": client.session_meta().unwrap().user_id,
2144                    "origin_server_ts": 12344445,
2145                })
2146                .to_string(),
2147            )
2148            .unwrap(),
2149        ];
2150        let mut response = response_with_room(room_id, http::response::Room::new());
2151        response
2152            .extensions
2153            .account_data
2154            .rooms
2155            .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2156
2157        client
2158            .process_sliding_sync(
2159                &response,
2160                &RequestedRequiredStates::default(),
2161                &client.state_store_lock().lock().await,
2162            )
2163            .await
2164            .expect("Failed to process sync");
2165
2166        // Then a room info notable update is received.
2167        assert_matches!(
2168            room_info_notable_update_stream.recv().await,
2169            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2170                assert_eq!(received_room_id, room_id);
2171                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2172            }
2173        );
2174        assert!(room_info_notable_update_stream.is_empty());
2175
2176        // When I receive a sliding sync response with a stable unread marker update,
2177        let stable_room_account_data_events = vec![
2178            Raw::from_json_string(
2179                json!({
2180                    "type": "m.marked_unread",
2181                    "event_id": "$1",
2182                    "content": { "unread": false },
2183                    "sender": client.session_meta().unwrap().user_id,
2184                    "origin_server_ts": 12344445,
2185                })
2186                .to_string(),
2187            )
2188            .unwrap(),
2189        ];
2190        response
2191            .extensions
2192            .account_data
2193            .rooms
2194            .insert(room_id.to_owned(), stable_room_account_data_events);
2195        client
2196            .process_sliding_sync(
2197                &response,
2198                &RequestedRequiredStates::default(),
2199                &client.state_store_lock().lock().await,
2200            )
2201            .await
2202            .expect("Failed to process sync");
2203
2204        // Then a room info notable update is received.
2205        assert_matches!(
2206            room_info_notable_update_stream.recv().await,
2207            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2208                assert_eq!(received_room_id, room_id);
2209                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2210            }
2211        );
2212        assert!(room_info_notable_update_stream.is_empty());
2213
2214        // When I receive a sliding sync response with an unstable unread
2215        // marker update again,
2216        response
2217            .extensions
2218            .account_data
2219            .rooms
2220            .insert(room_id.to_owned(), unstable_room_account_data_events);
2221        client
2222            .process_sliding_sync(
2223                &response,
2224                &RequestedRequiredStates::default(),
2225                &client.state_store_lock().lock().await,
2226            )
2227            .await
2228            .expect("Failed to process sync");
2229
2230        // There is no notable update.
2231        assert_matches!(
2232            room_info_notable_update_stream.recv().await,
2233            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2234                assert_eq!(received_room_id, room_id);
2235                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2236            }
2237        );
2238        assert!(room_info_notable_update_stream.is_empty());
2239
2240        // Finally, when I receive a sliding sync response with a stable unread marker
2241        // update again,
2242        let stable_room_account_data_events = vec![
2243            Raw::from_json_string(
2244                json!({
2245                    "type": "m.marked_unread",
2246                    "event_id": "$3",
2247                    "content": { "unread": true },
2248                    "sender": client.session_meta().unwrap().user_id,
2249                    "origin_server_ts": 12344445,
2250                })
2251                .to_string(),
2252            )
2253            .unwrap(),
2254        ];
2255        response
2256            .extensions
2257            .account_data
2258            .rooms
2259            .insert(room_id.to_owned(), stable_room_account_data_events);
2260        client
2261            .process_sliding_sync(
2262                &response,
2263                &RequestedRequiredStates::default(),
2264                &client.state_store_lock().lock().await,
2265            )
2266            .await
2267            .expect("Failed to process sync");
2268
2269        // Then a room info notable update is received.
2270        assert_matches!(
2271            room_info_notable_update_stream.recv().await,
2272            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2273                assert_eq!(received_room_id, room_id);
2274                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2275            }
2276        );
2277        assert!(room_info_notable_update_stream.is_empty());
2278    }
2279
2280    #[async_test]
2281    async fn test_pinned_events_are_updated_on_sync() {
2282        let user_a_id = user_id!("@a:e.uk");
2283        let client = logged_in_base_client(Some(user_a_id)).await;
2284        let room_id = room_id!("!r:e.uk");
2285        let pinned_event_id = owned_event_id!("$an-id:e.uk");
2286
2287        // Create room
2288        let mut room_response = http::response::Room::new();
2289        set_room_joined(&mut room_response, user_a_id);
2290        let response = response_with_room(room_id, room_response);
2291        client
2292            .process_sliding_sync(
2293                &response,
2294                &RequestedRequiredStates::default(),
2295                &client.state_store_lock().lock().await,
2296            )
2297            .await
2298            .expect("Failed to process sync");
2299
2300        // The newly created room has no pinned event ids
2301        let room = client.get_room(room_id).unwrap();
2302        let pinned_event_ids = room.pinned_event_ids();
2303        assert_matches!(pinned_event_ids, None);
2304
2305        // Load new pinned event id
2306        let mut room_response = http::response::Room::new();
2307        room_response.required_state.push(make_state_event(
2308            user_a_id,
2309            "",
2310            RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2311            None,
2312        ));
2313        let response = response_with_room(room_id, room_response);
2314        client
2315            .process_sliding_sync(
2316                &response,
2317                &RequestedRequiredStates::default(),
2318                &client.state_store_lock().lock().await,
2319            )
2320            .await
2321            .expect("Failed to process sync");
2322
2323        let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2324        assert_eq!(pinned_event_ids.len(), 1);
2325        assert_eq!(pinned_event_ids[0], pinned_event_id);
2326
2327        // Pinned event ids are now empty
2328        let mut room_response = http::response::Room::new();
2329        room_response.required_state.push(make_state_event(
2330            user_a_id,
2331            "",
2332            RoomPinnedEventsEventContent::new(Vec::new()),
2333            None,
2334        ));
2335        let response = response_with_room(room_id, room_response);
2336        client
2337            .process_sliding_sync(
2338                &response,
2339                &RequestedRequiredStates::default(),
2340                &client.state_store_lock().lock().await,
2341            )
2342            .await
2343            .expect("Failed to process sync");
2344        let pinned_event_ids = room.pinned_event_ids().unwrap();
2345        assert!(pinned_event_ids.is_empty());
2346    }
2347
2348    #[async_test]
2349    async fn test_dms_are_processed_in_any_sync_response() {
2350        let current_user_id = user_id!("@current:e.uk");
2351        let client = logged_in_base_client(Some(current_user_id)).await;
2352        let user_a_id = user_id!("@a:e.uk");
2353        let user_b_id = user_id!("@b:e.uk");
2354        let room_id_1 = room_id!("!r:e.uk");
2355        let room_id_2 = room_id!("!s:e.uk");
2356
2357        let mut room_response = http::response::Room::new();
2358        set_room_joined(&mut room_response, user_a_id);
2359        let mut response = response_with_room(room_id_1, room_response);
2360        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2361            BTreeMap::new();
2362        direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2363        direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2364        response
2365            .extensions
2366            .account_data
2367            .global
2368            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2369        client
2370            .process_sliding_sync(
2371                &response,
2372                &RequestedRequiredStates::default(),
2373                &client.state_store_lock().lock().await,
2374            )
2375            .await
2376            .expect("Failed to process sync");
2377
2378        let room_1 = client.get_room(room_id_1).unwrap();
2379        assert!(room_1.is_direct().await.unwrap());
2380
2381        // Now perform a sync without new account data
2382        let mut room_response = http::response::Room::new();
2383        set_room_joined(&mut room_response, user_b_id);
2384        let response = response_with_room(room_id_2, room_response);
2385        client
2386            .process_sliding_sync(
2387                &response,
2388                &RequestedRequiredStates::default(),
2389                &client.state_store_lock().lock().await,
2390            )
2391            .await
2392            .expect("Failed to process sync");
2393
2394        let room_2 = client.get_room(room_id_2).unwrap();
2395        assert!(room_2.is_direct().await.unwrap());
2396    }
2397
2398    #[async_test]
2399    async fn test_room_encryption_state_is_and_is_not_encrypted() {
2400        let user_id = user_id!("@raclette:patate");
2401        let client = logged_in_base_client(Some(user_id)).await;
2402        let room_id_0 = room_id!("!r0");
2403        let room_id_1 = room_id!("!r1");
2404        let room_id_2 = room_id!("!r2");
2405
2406        // A room is considered encrypted when it receives a `m.room.encryption` event,
2407        // period.
2408        //
2409        // A room is considered **not** encrypted when it receives no
2410        // `m.room.encryption` event but it was requested, period.
2411        //
2412        // We are going to test three rooms:
2413        //
2414        // - two of them receive a `m.room.encryption` event
2415        // - the last one does not receive a `m.room.encryption`.
2416        // - the first one is configured with a `required_state` for this event, the
2417        //   others have nothing.
2418        //
2419        // The trick is that, since sliding sync makes an union of all the
2420        // `required_state`s, then all rooms are technically requesting a
2421        // `m.room.encryption`.
2422        let requested_required_states = RequestedRequiredStates::from(&{
2423            let mut request = http::Request::new();
2424
2425            request.room_subscriptions.insert(room_id_0.to_owned(), {
2426                let mut room_subscription = http::request::RoomSubscription::default();
2427
2428                room_subscription
2429                    .required_state
2430                    .push((StateEventType::RoomEncryption, "".to_owned()));
2431
2432                room_subscription
2433            });
2434
2435            request
2436        });
2437
2438        let mut response = http::Response::new("0".to_owned());
2439
2440        // Create two rooms that are encrypted, i.e. they have a `m.room.encryption`
2441        // state event in their `required_state`. Create a third room that is not
2442        // encrypted, i.e. it doesn't have a `m.room.encryption` state event.
2443        {
2444            let not_encrypted_room = http::response::Room::new();
2445            let mut encrypted_room = http::response::Room::new();
2446            set_room_is_encrypted(&mut encrypted_room, user_id);
2447
2448            response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2449            response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2450            response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2451        }
2452
2453        client
2454            .process_sliding_sync(
2455                &response,
2456                &requested_required_states,
2457                &client.state_store_lock().lock().await,
2458            )
2459            .await
2460            .expect("Failed to process sync");
2461
2462        // They are both encrypted, yepee.
2463        assert_matches!(
2464            client.get_room(room_id_0).unwrap().encryption_state(),
2465            EncryptionState::Encrypted
2466        );
2467        assert_matches!(
2468            client.get_room(room_id_1).unwrap().encryption_state(),
2469            EncryptionState::Encrypted
2470        );
2471        // This one is not encrypted because it has received nothing.
2472        assert_matches!(
2473            client.get_room(room_id_2).unwrap().encryption_state(),
2474            EncryptionState::NotEncrypted
2475        )
2476    }
2477
2478    #[async_test]
2479    async fn test_room_encryption_state_is_unknown() {
2480        let user_id = user_id!("@raclette:patate");
2481        let client = logged_in_base_client(Some(user_id)).await;
2482        let room_id_0 = room_id!("!r0");
2483        let room_id_1 = room_id!("!r1");
2484
2485        // A room is considered encrypted when it receives a `m.room.encryption` event,
2486        // period.
2487        //
2488        // A room is considered **not** encrypted when it receives no
2489        // `m.room.encryption` event but it was requested, period.
2490        //
2491        // We are going to test two rooms:
2492        //
2493        // - one that receives a `m.room.encryption` event,
2494        // - one that receives nothing,
2495        // - none of them have requested the state event.
2496
2497        let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2498
2499        let mut response = http::Response::new("0".to_owned());
2500
2501        // Create two rooms with and without a `m.room.encryption` event.
2502        {
2503            let not_encrypted_room = http::response::Room::new();
2504            let mut encrypted_room = http::response::Room::new();
2505            set_room_is_encrypted(&mut encrypted_room, user_id);
2506
2507            response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2508            response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2509        }
2510
2511        client
2512            .process_sliding_sync(
2513                &response,
2514                &requested_required_states,
2515                &client.state_store_lock().lock().await,
2516            )
2517            .await
2518            .expect("Failed to process sync");
2519
2520        // Encrypted, because the presence of a `m.room.encryption` always mean the room
2521        // is encrypted.
2522        assert_matches!(
2523            client.get_room(room_id_0).unwrap().encryption_state(),
2524            EncryptionState::Encrypted
2525        );
2526        // Unknown, because the absence of `m.room.encryption` when not requested
2527        // means we don't know what the state is.
2528        assert_matches!(
2529            client.get_room(room_id_1).unwrap().encryption_state(),
2530            EncryptionState::Unknown
2531        );
2532    }
2533
2534    async fn membership(
2535        client: &BaseClient,
2536        room_id: &RoomId,
2537        user_id: &UserId,
2538    ) -> MembershipState {
2539        let room = client.get_room(room_id).expect("Room not found!");
2540        let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2541        member.membership().clone()
2542    }
2543
2544    fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2545        let room = client.get_room(room_id).expect("Room not found!");
2546        room.direct_targets()
2547    }
2548
2549    /// Create a DM with the other user, setting our membership to Join and
2550    /// theirs to other_state
2551    async fn create_dm(
2552        client: &BaseClient,
2553        room_id: &RoomId,
2554        my_id: &UserId,
2555        their_id: &UserId,
2556        other_state: MembershipState,
2557    ) {
2558        let mut room = http::response::Room::new();
2559        set_room_joined(&mut room, my_id);
2560
2561        match other_state {
2562            MembershipState::Join => {
2563                room.joined_count = Some(uint!(2));
2564                room.invited_count = None;
2565            }
2566
2567            MembershipState::Invite => {
2568                room.joined_count = Some(uint!(1));
2569                room.invited_count = Some(uint!(1));
2570            }
2571
2572            _ => {
2573                room.joined_count = Some(uint!(1));
2574                room.invited_count = None;
2575            }
2576        }
2577
2578        room.required_state.push(make_membership_event(their_id, other_state));
2579
2580        let mut response = response_with_room(room_id, room);
2581        set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2582        client
2583            .process_sliding_sync(
2584                &response,
2585                &RequestedRequiredStates::default(),
2586                &client.state_store_lock().lock().await,
2587            )
2588            .await
2589            .expect("Failed to process sync");
2590    }
2591
2592    /// Set this user's membership within this room to new_state
2593    async fn update_room_membership(
2594        client: &BaseClient,
2595        room_id: &RoomId,
2596        user_id: &UserId,
2597        new_state: MembershipState,
2598    ) {
2599        let mut room = http::response::Room::new();
2600        room.required_state.push(make_membership_event(user_id, new_state));
2601        let response = response_with_room(room_id, room);
2602        client
2603            .process_sliding_sync(
2604                &response,
2605                &RequestedRequiredStates::default(),
2606                &client.state_store_lock().lock().await,
2607            )
2608            .await
2609            .expect("Failed to process sync");
2610    }
2611
2612    fn set_direct_with(
2613        response: &mut http::Response,
2614        user_id: OwnedUserId,
2615        room_ids: Vec<OwnedRoomId>,
2616    ) {
2617        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2618            BTreeMap::new();
2619        direct_content.insert(user_id.into(), room_ids);
2620        response
2621            .extensions
2622            .account_data
2623            .global
2624            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2625    }
2626
2627    fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2628        let mut response = http::Response::new("5".to_owned());
2629        response.rooms.insert(room_id.to_owned(), room);
2630        response
2631    }
2632
2633    fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2634        let mut room = http::response::Room::new();
2635
2636        let mut avatar_event_content = RoomAvatarEventContent::new();
2637        avatar_event_content.url = Some(avatar_uri.to_owned());
2638
2639        room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2640
2641        room
2642    }
2643
2644    fn room_with_canonical_alias(
2645        room_alias_id: &RoomAliasId,
2646        user_id: &UserId,
2647    ) -> http::response::Room {
2648        let mut room = http::response::Room::new();
2649
2650        let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2651        canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2652
2653        room.required_state.push(make_state_event(
2654            user_id,
2655            "",
2656            canonical_alias_event_content,
2657            None,
2658        ));
2659
2660        room
2661    }
2662
2663    fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2664        let mut room = http::response::Room::new();
2665
2666        let name_event_content = RoomNameEventContent::new(name.to_owned());
2667
2668        room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2669
2670        room
2671    }
2672
2673    fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2674        room.required_state.push(make_state_event(
2675            sender,
2676            "",
2677            RoomNameEventContent::new(name),
2678            None,
2679        ));
2680    }
2681
2682    fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2683        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2684        // room. Just the type is supplied.
2685
2686        let evt = Raw::new(&json!({
2687            "type": "m.room.member",
2688            "sender": inviter,
2689            "content": {
2690                "is_direct": true,
2691                "membership": "invite",
2692            },
2693            "state_key": invitee,
2694        }))
2695        .expect("Failed to make raw event")
2696        .cast_unchecked();
2697
2698        room.invite_state = Some(vec![evt]);
2699
2700        // We expect that there will also be an invite event in the required_state,
2701        // assuming you've asked for this type of event.
2702        room.required_state.push(make_state_event(
2703            inviter,
2704            invitee.as_str(),
2705            RoomMemberEventContent::new(MembershipState::Invite),
2706            None,
2707        ));
2708    }
2709
2710    fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2711        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2712        // room. Just the type is supplied.
2713
2714        let evt = Raw::new(&json!({
2715            "type": "m.room.member",
2716            "sender": knocker,
2717            "content": {
2718                "is_direct": true,
2719                "membership": "knock",
2720            },
2721            "state_key": knocker,
2722        }))
2723        .expect("Failed to make raw event")
2724        .cast_unchecked();
2725
2726        room.invite_state = Some(vec![evt]);
2727    }
2728
2729    fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2730        room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2731    }
2732
2733    fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2734        room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2735    }
2736
2737    fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2738        room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2739    }
2740
2741    fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2742        room.required_state.push(make_encryption_event(user_id));
2743    }
2744
2745    fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2746        make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2747    }
2748
2749    fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2750        make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2751    }
2752
2753    fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2754        Raw::new(&json!({
2755            "type": content.event_type(),
2756            "content": content,
2757        }))
2758        .expect("Failed to create account data event")
2759        .cast_unchecked()
2760    }
2761
2762    fn make_state_event<C: StateEventContent, E>(
2763        sender: &UserId,
2764        state_key: &str,
2765        content: C,
2766        prev_content: Option<C>,
2767    ) -> Raw<E> {
2768        let unsigned = if let Some(prev_content) = prev_content {
2769            json!({ "prev_content": prev_content })
2770        } else {
2771            json!({})
2772        };
2773
2774        Raw::new(&json!({
2775            "type": content.event_type(),
2776            "state_key": state_key,
2777            "content": content,
2778            "event_id": event_id!("$evt"),
2779            "sender": sender,
2780            "origin_server_ts": 10,
2781            "unsigned": unsigned,
2782        }))
2783        .expect("Failed to create state event")
2784        .cast_unchecked()
2785    }
2786}