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