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                    let _ = self.state_store.room_info_notable_update_sender.send(
238                        crate::RoomInfoNotableUpdate {
239                            room_id: room.room_id().to_owned(),
240                            reasons: crate::RoomInfoNotableUpdateReasons::HEROES,
241                        },
242                    );
243                }
244            }
245        }
246
247        let mut context = processors::Context::default();
248
249        // Now that all the rooms information have been saved, update the display name
250        // of the updated rooms (which relies on information stored in the database).
251        processors::room::display_name::update_for_rooms(
252            &mut context,
253            &room_updates,
254            &self.state_store,
255        )
256        .await;
257
258        // Save the new display name updates if any.
259        processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
260
261        Ok(SyncResponse {
262            rooms: room_updates,
263            notifications,
264            presence: Default::default(),
265            account_data: extensions.account_data.global.clone(),
266            to_device: Default::default(),
267        })
268    }
269
270    /// Process the `receipts` extension, and compute (and save) the unread
271    /// counts based on read receipts, for a particular room.
272    #[doc(hidden)]
273    pub async fn process_sliding_sync_receipts_extension_for_room(
274        &self,
275        room_id: &OwnedRoomId,
276        response: &http::Response,
277        state_store_guard: &MutexGuard<'_, ()>,
278    ) -> Result<Option<Raw<SyncReceiptEvent>>> {
279        let mut context = processors::Context::default();
280
281        let mut save_context = false;
282
283        // Handle the receipt ephemeral event.
284        let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
285            response.extensions.receipts.rooms.get(room_id)
286        {
287            processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
288                &mut context,
289                room_id,
290                receipt_ephemeral_event,
291            );
292            save_context = true;
293            Some(receipt_ephemeral_event.clone())
294        } else {
295            None
296        };
297
298        // Save the new `RoomInfo` if updated.
299        if save_context {
300            processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
301        }
302
303        Ok(receipt_ephemeral_event)
304    }
305}
306
307#[cfg(all(test, not(target_family = "wasm")))]
308mod tests {
309    use std::collections::{BTreeMap, HashSet};
310
311    use assert_matches::assert_matches;
312    use matrix_sdk_test::async_test;
313    use ruma::{
314        JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
315        api::client::sync::sync_events::UnreadNotificationsCount,
316        assign, event_id,
317        events::{
318            GlobalAccountDataEventContent, StateEventContent, StateEventType,
319            direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
320            room::{
321                avatar::RoomAvatarEventContent,
322                canonical_alias::RoomCanonicalAliasEventContent,
323                encryption::RoomEncryptionEventContent,
324                member::{MembershipState, RoomMemberEventContent},
325                name::RoomNameEventContent,
326                pinned_events::RoomPinnedEventsEventContent,
327            },
328        },
329        mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
330        profile::{ProfileFieldName, UserProfileChanges, UserProfileUpdate},
331        room_alias_id, room_id,
332        serde::Raw,
333        uint, user_id,
334    };
335    use serde_json::json;
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 mut room_info_notable_update = client.room_info_notable_update_receiver();
1686
1687        // When a subsequent sync carries only a profiles-extension update for Alice.
1688        let mut response = http::Response::new("1".to_owned());
1689        response.extensions.profiles.users.insert(
1690            alice.clone(),
1691            make_profile_update(ProfileFieldName::Status, json!({ "text": "Away", "emoji": "🌴" })),
1692        );
1693        client
1694            .process_sliding_sync(
1695                &response,
1696                &RequestedRequiredStates::default(),
1697                &client.state_store_lock().lock().await,
1698            )
1699            .await
1700            .expect("Failed to process sync");
1701
1702        // Then a `HEROES` notable update is emitted for the room, so consumers
1703        // re-read the hero profiles.
1704        assert_matches!(
1705            room_info_notable_update.recv().await,
1706            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1707                assert_eq!(received_room_id, room_id);
1708                assert!(reasons.contains(RoomInfoNotableUpdateReasons::HEROES));
1709            }
1710        );
1711        assert!(room_info_notable_update.is_empty());
1712    }
1713
1714    #[async_test]
1715    async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1716        // Given a logged-in client
1717        let client = logged_in_base_client(None).await;
1718        let room_id = room_id!("!r:e.uk");
1719
1720        // When I send sliding sync response containing a room with a recency stamp
1721        let room = assign!(http::response::Room::new(), {
1722            bump_stamp: Some(42u32.into()),
1723        });
1724        let response = response_with_room(room_id, room);
1725        client
1726            .process_sliding_sync(
1727                &response,
1728                &RequestedRequiredStates::default(),
1729                &client.state_store_lock().lock().await,
1730            )
1731            .await
1732            .expect("Failed to process sync");
1733
1734        // Then the room in the client has the recency stamp
1735        let client_room = client.get_room(room_id).expect("No room found");
1736        assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1737    }
1738
1739    #[async_test]
1740    async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1741        // Given a logged-in client
1742        let client = logged_in_base_client(None).await;
1743        let room_id = room_id!("!r:e.uk");
1744
1745        {
1746            // When I send sliding sync response containing a room with a recency stamp
1747            let room = assign!(http::response::Room::new(), {
1748                bump_stamp: Some(42u32.into()),
1749            });
1750            let response = response_with_room(room_id, room);
1751            client
1752                .process_sliding_sync(
1753                    &response,
1754                    &RequestedRequiredStates::default(),
1755                    &client.state_store_lock().lock().await,
1756                )
1757                .await
1758                .expect("Failed to process sync");
1759
1760            // Then the room in the client has the recency stamp
1761            let client_room = client.get_room(room_id).expect("No room found");
1762            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1763        }
1764
1765        {
1766            // When I send sliding sync response containing a room with NO recency stamp
1767            let room = assign!(http::response::Room::new(), {
1768                bump_stamp: None,
1769            });
1770            let response = response_with_room(room_id, room);
1771            client
1772                .process_sliding_sync(
1773                    &response,
1774                    &RequestedRequiredStates::default(),
1775                    &client.state_store_lock().lock().await,
1776                )
1777                .await
1778                .expect("Failed to process sync");
1779
1780            // Then the room in the client has the previous recency stamp
1781            let client_room = client.get_room(room_id).expect("No room found");
1782            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1783        }
1784
1785        {
1786            // When I send sliding sync response containing a room with a NEW recency
1787            // timestamp
1788            let room = assign!(http::response::Room::new(), {
1789                bump_stamp: Some(153u32.into()),
1790            });
1791            let response = response_with_room(room_id, room);
1792            client
1793                .process_sliding_sync(
1794                    &response,
1795                    &RequestedRequiredStates::default(),
1796                    &client.state_store_lock().lock().await,
1797                )
1798                .await
1799                .expect("Failed to process sync");
1800
1801            // Then the room in the client has the recency stamp
1802            let client_room = client.get_room(room_id).expect("No room found");
1803            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1804        }
1805    }
1806
1807    #[async_test]
1808    async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1809        // Given a logged-in client
1810        let client = logged_in_base_client(None).await;
1811        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1812        let room_id = room_id!("!r:e.uk");
1813
1814        // When I send sliding sync response containing a room with a recency stamp.
1815        let room = assign!(http::response::Room::new(), {
1816            bump_stamp: Some(42u32.into()),
1817        });
1818        let response = response_with_room(room_id, room);
1819        client
1820            .process_sliding_sync(
1821                &response,
1822                &RequestedRequiredStates::default(),
1823                &client.state_store_lock().lock().await,
1824            )
1825            .await
1826            .expect("Failed to process sync");
1827
1828        // Then a room info notable update is NOT received, because it's the first time
1829        // the room is seen.
1830        assert_matches!(
1831            room_info_notable_update_stream.recv().await,
1832            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1833                assert_eq!(received_room_id, room_id);
1834                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1835            }
1836        );
1837        assert_matches!(
1838            room_info_notable_update_stream.recv().await,
1839            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1840                assert_eq!(received_room_id, room_id);
1841                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1842            }
1843        );
1844        assert!(room_info_notable_update_stream.is_empty());
1845
1846        // When I send sliding sync response containing a room with a recency stamp.
1847        let room = assign!(http::response::Room::new(), {
1848            bump_stamp: Some(43u32.into()),
1849        });
1850        let response = response_with_room(room_id, room);
1851        client
1852            .process_sliding_sync(
1853                &response,
1854                &RequestedRequiredStates::default(),
1855                &client.state_store_lock().lock().await,
1856            )
1857            .await
1858            .expect("Failed to process sync");
1859
1860        // Then a room info notable update is received.
1861        assert_matches!(
1862            room_info_notable_update_stream.recv().await,
1863            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1864                assert_eq!(received_room_id, room_id);
1865                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1866            }
1867        );
1868        assert!(room_info_notable_update_stream.is_empty());
1869    }
1870
1871    #[async_test]
1872    async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1873        // Given a logged-in client
1874        let client = logged_in_base_client(None).await;
1875        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1876
1877        // When I send sliding sync response containing a new room.
1878        let room_id = room_id!("!r:e.uk");
1879        let room = http::response::Room::new();
1880        let response = response_with_room(room_id, room);
1881        client
1882            .process_sliding_sync(
1883                &response,
1884                &RequestedRequiredStates::default(),
1885                &client.state_store_lock().lock().await,
1886            )
1887            .await
1888            .expect("Failed to process sync");
1889
1890        // Other notable update reason. We don't really care about them here.
1891        assert_matches!(
1892            room_info_notable_update_stream.recv().await,
1893            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1894                assert_eq!(received_room_id, room_id);
1895                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1896            }
1897        );
1898        assert_matches!(
1899            room_info_notable_update_stream.recv().await,
1900            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1901                assert_eq!(received_room_id, room_id);
1902                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1903            }
1904        );
1905
1906        // Send sliding sync response containing a membership event with 'join' value.
1907        let room_id = room_id!("!r:e.uk");
1908        let events = vec![
1909            Raw::from_json_string(
1910                json!({
1911                    "type": "m.room.member",
1912                    "event_id": "$3",
1913                    "content": { "membership": "join" },
1914                    "sender": "@u:h.uk",
1915                    "origin_server_ts": 12344445,
1916                    "state_key": "@u:e.uk",
1917                })
1918                .to_string(),
1919            )
1920            .unwrap(),
1921        ];
1922        let room = assign!(http::response::Room::new(), {
1923            required_state: events,
1924        });
1925        let response = response_with_room(room_id, room);
1926        client
1927            .process_sliding_sync(
1928                &response,
1929                &RequestedRequiredStates::default(),
1930                &client.state_store_lock().lock().await,
1931            )
1932            .await
1933            .expect("Failed to process sync");
1934
1935        // Room was already joined, no `MEMBERSHIP` update should be triggered here
1936        assert_matches!(
1937            room_info_notable_update_stream.recv().await,
1938            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1939                assert_eq!(received_room_id, room_id);
1940                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1941            }
1942        );
1943        assert!(room_info_notable_update_stream.is_empty());
1944
1945        let events = vec![
1946            Raw::from_json_string(
1947                json!({
1948                    "type": "m.room.member",
1949                    "event_id": "$3",
1950                    "content": { "membership": "leave" },
1951                    "sender": "@u:h.uk",
1952                    "origin_server_ts": 12344445,
1953                    "state_key": "@u:e.uk",
1954                })
1955                .to_string(),
1956            )
1957            .unwrap(),
1958        ];
1959        let room = assign!(http::response::Room::new(), {
1960            required_state: events,
1961        });
1962        let response = response_with_room(room_id, room);
1963        client
1964            .process_sliding_sync(
1965                &response,
1966                &RequestedRequiredStates::default(),
1967                &client.state_store_lock().lock().await,
1968            )
1969            .await
1970            .expect("Failed to process sync");
1971
1972        // Then a room info notable update is received.
1973        assert_matches!(
1974            room_info_notable_update_stream.recv().await,
1975            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1976                assert_eq!(received_room_id, room_id);
1977                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1978            }
1979        );
1980        assert!(room_info_notable_update_stream.is_empty());
1981    }
1982
1983    #[async_test]
1984    async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1985        // Given a logged-in client,
1986        let client = logged_in_base_client(None).await;
1987        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1988
1989        // When I receive a sliding sync response containing a new room,
1990        let room_id = room_id!("!r:e.uk");
1991        let room = http::response::Room::new();
1992        let response = response_with_room(room_id, room);
1993        client
1994            .process_sliding_sync(
1995                &response,
1996                &RequestedRequiredStates::default(),
1997                &client.state_store_lock().lock().await,
1998            )
1999            .await
2000            .expect("Failed to process sync");
2001
2002        // Other notable updates are received, but not the ones we are interested by.
2003        assert_matches!(
2004            room_info_notable_update_stream.recv().await,
2005            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2006                assert_eq!(received_room_id, room_id);
2007                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2008            }
2009        );
2010        assert_matches!(
2011            room_info_notable_update_stream.recv().await,
2012            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2013                assert_eq!(received_room_id, room_id);
2014                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2015            }
2016        );
2017        assert!(room_info_notable_update_stream.is_empty());
2018
2019        // When I receive a sliding sync response containing one update about an unread
2020        // marker,
2021        let room_id = room_id!("!r:e.uk");
2022        let room_account_data_events = vec![
2023            Raw::from_json_string(
2024                json!({
2025                    "type": "m.marked_unread",
2026                    "event_id": "$1",
2027                    "content": { "unread": true },
2028                    "sender": client.session_meta().unwrap().user_id,
2029                    "origin_server_ts": 12344445,
2030                })
2031                .to_string(),
2032            )
2033            .unwrap(),
2034        ];
2035        let mut response = response_with_room(room_id, http::response::Room::new());
2036        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2037
2038        client
2039            .process_sliding_sync(
2040                &response,
2041                &RequestedRequiredStates::default(),
2042                &client.state_store_lock().lock().await,
2043            )
2044            .await
2045            .expect("Failed to process sync");
2046
2047        // Then a room info notable update is received.
2048        assert_matches!(
2049            room_info_notable_update_stream.recv().await,
2050            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2051                assert_eq!(received_room_id, room_id);
2052                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2053            }
2054        );
2055
2056        // But getting it again won't trigger a new notable update…
2057        client
2058            .process_sliding_sync(
2059                &response,
2060                &RequestedRequiredStates::default(),
2061                &client.state_store_lock().lock().await,
2062            )
2063            .await
2064            .expect("Failed to process sync");
2065
2066        assert_matches!(
2067            room_info_notable_update_stream.recv().await,
2068            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2069                assert_eq!(received_room_id, room_id);
2070                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2071            }
2072        );
2073        assert!(room_info_notable_update_stream.is_empty());
2074
2075        // …Unless its value changes!
2076        let room_account_data_events = vec![
2077            Raw::from_json_string(
2078                json!({
2079                    "type": "m.marked_unread",
2080                    "event_id": "$1",
2081                    "content": { "unread": false },
2082                    "sender": client.session_meta().unwrap().user_id,
2083                    "origin_server_ts": 12344445,
2084                })
2085                .to_string(),
2086            )
2087            .unwrap(),
2088        ];
2089        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2090        client
2091            .process_sliding_sync(
2092                &response,
2093                &RequestedRequiredStates::default(),
2094                &client.state_store_lock().lock().await,
2095            )
2096            .await
2097            .expect("Failed to process sync");
2098
2099        assert_matches!(
2100            room_info_notable_update_stream.recv().await,
2101            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2102                assert_eq!(received_room_id, room_id);
2103                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2104            }
2105        );
2106        assert!(room_info_notable_update_stream.is_empty());
2107    }
2108
2109    #[async_test]
2110    async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
2111        // Given a logged-in client,
2112        let client = logged_in_base_client(None).await;
2113        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2114
2115        // When I receive a sliding sync response containing a new room,
2116        let room_id = room_id!("!r:e.uk");
2117        let room = http::response::Room::new();
2118        let response = response_with_room(room_id, room);
2119        client
2120            .process_sliding_sync(
2121                &response,
2122                &RequestedRequiredStates::default(),
2123                &client.state_store_lock().lock().await,
2124            )
2125            .await
2126            .expect("Failed to process sync");
2127
2128        // Other notable updates are received, but not the ones we are interested by.
2129        assert_matches!(
2130            room_info_notable_update_stream.recv().await,
2131            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2132                assert_eq!(received_room_id, room_id);
2133                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2134            }
2135        );
2136        assert_matches!(
2137            room_info_notable_update_stream.recv().await,
2138            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2139                assert_eq!(received_room_id, room_id);
2140                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2141            }
2142        );
2143        assert!(room_info_notable_update_stream.is_empty());
2144
2145        // When I receive a sliding sync response containing an `m.fully_read`
2146        // account-data event,
2147        let room_account_data_events = vec![
2148            Raw::from_json_string(
2149                json!({
2150                    "type": "m.fully_read",
2151                    "content": { "event_id": "$first" },
2152                })
2153                .to_string(),
2154            )
2155            .unwrap(),
2156        ];
2157        let mut response = response_with_room(room_id, http::response::Room::new());
2158        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2159
2160        client
2161            .process_sliding_sync(
2162                &response,
2163                &RequestedRequiredStates::default(),
2164                &client.state_store_lock().lock().await,
2165            )
2166            .await
2167            .expect("Failed to process sync");
2168
2169        // Then a `FULLY_READ` notable update is received,
2170        assert_matches!(
2171            room_info_notable_update_stream.recv().await,
2172            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2173                assert_eq!(received_room_id, room_id);
2174                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2175            }
2176        );
2177
2178        let room = client.get_room(room_id).expect("room should exist");
2179        assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2180
2181        // But getting the same value again won't trigger a new notable update…
2182        client
2183            .process_sliding_sync(
2184                &response,
2185                &RequestedRequiredStates::default(),
2186                &client.state_store_lock().lock().await,
2187            )
2188            .await
2189            .expect("Failed to process sync");
2190
2191        assert_matches!(
2192            room_info_notable_update_stream.recv().await,
2193            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2194                assert_eq!(received_room_id, room_id);
2195                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2196            }
2197        );
2198        assert!(room_info_notable_update_stream.is_empty());
2199
2200        // … Unless the event ID changes!
2201        let room_account_data_events = vec![
2202            Raw::from_json_string(
2203                json!({
2204                    "type": "m.fully_read",
2205                    "content": { "event_id": "$second" },
2206                })
2207                .to_string(),
2208            )
2209            .unwrap(),
2210        ];
2211        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2212        client
2213            .process_sliding_sync(
2214                &response,
2215                &RequestedRequiredStates::default(),
2216                &client.state_store_lock().lock().await,
2217            )
2218            .await
2219            .expect("Failed to process sync");
2220
2221        assert_matches!(
2222            room_info_notable_update_stream.recv().await,
2223            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2224                assert_eq!(received_room_id, room_id);
2225                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2226            }
2227        );
2228        assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2229        assert!(room_info_notable_update_stream.is_empty());
2230    }
2231
2232    #[async_test]
2233    async fn test_unstable_unread_marker_is_ignored_after_stable() {
2234        // Given a logged-in client,
2235        let client = logged_in_base_client(None).await;
2236        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2237
2238        // When I receive a sliding sync response containing a new room,
2239        let room_id = room_id!("!r:e.uk");
2240        let room = http::response::Room::new();
2241        let response = response_with_room(room_id, room);
2242        client
2243            .process_sliding_sync(
2244                &response,
2245                &RequestedRequiredStates::default(),
2246                &client.state_store_lock().lock().await,
2247            )
2248            .await
2249            .expect("Failed to process sync");
2250
2251        // Other notable updates are received, but not the ones we are interested by.
2252        assert_matches!(
2253            room_info_notable_update_stream.recv().await,
2254            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2255                assert_eq!(received_room_id, room_id);
2256                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2257            }
2258        );
2259        assert_matches!(
2260            room_info_notable_update_stream.recv().await,
2261            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2262                assert_eq!(received_room_id, room_id);
2263                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2264            }
2265        );
2266        assert!(room_info_notable_update_stream.is_empty());
2267
2268        // When I receive a sliding sync response containing one update about an
2269        // unstable unread marker,
2270        let room_id = room_id!("!r:e.uk");
2271        let unstable_room_account_data_events = vec![
2272            Raw::from_json_string(
2273                json!({
2274                    "type": "com.famedly.marked_unread",
2275                    "event_id": "$1",
2276                    "content": { "unread": true },
2277                    "sender": client.session_meta().unwrap().user_id,
2278                    "origin_server_ts": 12344445,
2279                })
2280                .to_string(),
2281            )
2282            .unwrap(),
2283        ];
2284        let mut response = response_with_room(room_id, http::response::Room::new());
2285        response
2286            .extensions
2287            .account_data
2288            .rooms
2289            .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2290
2291        client
2292            .process_sliding_sync(
2293                &response,
2294                &RequestedRequiredStates::default(),
2295                &client.state_store_lock().lock().await,
2296            )
2297            .await
2298            .expect("Failed to process sync");
2299
2300        // Then a room info notable update is received.
2301        assert_matches!(
2302            room_info_notable_update_stream.recv().await,
2303            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2304                assert_eq!(received_room_id, room_id);
2305                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2306            }
2307        );
2308        assert!(room_info_notable_update_stream.is_empty());
2309
2310        // When I receive a sliding sync response with a stable unread marker update,
2311        let stable_room_account_data_events = vec![
2312            Raw::from_json_string(
2313                json!({
2314                    "type": "m.marked_unread",
2315                    "event_id": "$1",
2316                    "content": { "unread": false },
2317                    "sender": client.session_meta().unwrap().user_id,
2318                    "origin_server_ts": 12344445,
2319                })
2320                .to_string(),
2321            )
2322            .unwrap(),
2323        ];
2324        response
2325            .extensions
2326            .account_data
2327            .rooms
2328            .insert(room_id.to_owned(), stable_room_account_data_events);
2329        client
2330            .process_sliding_sync(
2331                &response,
2332                &RequestedRequiredStates::default(),
2333                &client.state_store_lock().lock().await,
2334            )
2335            .await
2336            .expect("Failed to process sync");
2337
2338        // Then a room info notable update is received.
2339        assert_matches!(
2340            room_info_notable_update_stream.recv().await,
2341            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2342                assert_eq!(received_room_id, room_id);
2343                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2344            }
2345        );
2346        assert!(room_info_notable_update_stream.is_empty());
2347
2348        // When I receive a sliding sync response with an unstable unread
2349        // marker update again,
2350        response
2351            .extensions
2352            .account_data
2353            .rooms
2354            .insert(room_id.to_owned(), unstable_room_account_data_events);
2355        client
2356            .process_sliding_sync(
2357                &response,
2358                &RequestedRequiredStates::default(),
2359                &client.state_store_lock().lock().await,
2360            )
2361            .await
2362            .expect("Failed to process sync");
2363
2364        // There is no notable update.
2365        assert_matches!(
2366            room_info_notable_update_stream.recv().await,
2367            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2368                assert_eq!(received_room_id, room_id);
2369                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2370            }
2371        );
2372        assert!(room_info_notable_update_stream.is_empty());
2373
2374        // Finally, when I receive a sliding sync response with a stable unread marker
2375        // update again,
2376        let stable_room_account_data_events = vec![
2377            Raw::from_json_string(
2378                json!({
2379                    "type": "m.marked_unread",
2380                    "event_id": "$3",
2381                    "content": { "unread": true },
2382                    "sender": client.session_meta().unwrap().user_id,
2383                    "origin_server_ts": 12344445,
2384                })
2385                .to_string(),
2386            )
2387            .unwrap(),
2388        ];
2389        response
2390            .extensions
2391            .account_data
2392            .rooms
2393            .insert(room_id.to_owned(), stable_room_account_data_events);
2394        client
2395            .process_sliding_sync(
2396                &response,
2397                &RequestedRequiredStates::default(),
2398                &client.state_store_lock().lock().await,
2399            )
2400            .await
2401            .expect("Failed to process sync");
2402
2403        // Then a room info notable update is received.
2404        assert_matches!(
2405            room_info_notable_update_stream.recv().await,
2406            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2407                assert_eq!(received_room_id, room_id);
2408                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2409            }
2410        );
2411        assert!(room_info_notable_update_stream.is_empty());
2412    }
2413
2414    #[async_test]
2415    async fn test_pinned_events_are_updated_on_sync() {
2416        let user_a_id = user_id!("@a:e.uk");
2417        let client = logged_in_base_client(Some(user_a_id)).await;
2418        let room_id = room_id!("!r:e.uk");
2419        let pinned_event_id = owned_event_id!("$an-id:e.uk");
2420
2421        // Create room
2422        let mut room_response = http::response::Room::new();
2423        set_room_joined(&mut room_response, user_a_id);
2424        let response = response_with_room(room_id, room_response);
2425        client
2426            .process_sliding_sync(
2427                &response,
2428                &RequestedRequiredStates::default(),
2429                &client.state_store_lock().lock().await,
2430            )
2431            .await
2432            .expect("Failed to process sync");
2433
2434        // The newly created room has no pinned event ids
2435        let room = client.get_room(room_id).unwrap();
2436        let pinned_event_ids = room.pinned_event_ids();
2437        assert_matches!(pinned_event_ids, None);
2438
2439        // Load new pinned event id
2440        let mut room_response = http::response::Room::new();
2441        room_response.required_state.push(make_state_event(
2442            user_a_id,
2443            "",
2444            RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2445            None,
2446        ));
2447        let response = response_with_room(room_id, room_response);
2448        client
2449            .process_sliding_sync(
2450                &response,
2451                &RequestedRequiredStates::default(),
2452                &client.state_store_lock().lock().await,
2453            )
2454            .await
2455            .expect("Failed to process sync");
2456
2457        let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2458        assert_eq!(pinned_event_ids.len(), 1);
2459        assert_eq!(pinned_event_ids[0], pinned_event_id);
2460
2461        // Pinned event ids are now empty
2462        let mut room_response = http::response::Room::new();
2463        room_response.required_state.push(make_state_event(
2464            user_a_id,
2465            "",
2466            RoomPinnedEventsEventContent::new(Vec::new()),
2467            None,
2468        ));
2469        let response = response_with_room(room_id, room_response);
2470        client
2471            .process_sliding_sync(
2472                &response,
2473                &RequestedRequiredStates::default(),
2474                &client.state_store_lock().lock().await,
2475            )
2476            .await
2477            .expect("Failed to process sync");
2478        let pinned_event_ids = room.pinned_event_ids().unwrap();
2479        assert!(pinned_event_ids.is_empty());
2480    }
2481
2482    #[async_test]
2483    async fn test_dms_are_processed_in_any_sync_response() {
2484        let current_user_id = user_id!("@current:e.uk");
2485        let client = logged_in_base_client(Some(current_user_id)).await;
2486        let user_a_id = user_id!("@a:e.uk");
2487        let user_b_id = user_id!("@b:e.uk");
2488        let room_id_1 = room_id!("!r:e.uk");
2489        let room_id_2 = room_id!("!s:e.uk");
2490
2491        let mut room_response = http::response::Room::new();
2492        set_room_joined(&mut room_response, user_a_id);
2493        let mut response = response_with_room(room_id_1, room_response);
2494        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2495            BTreeMap::new();
2496        direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2497        direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2498        response
2499            .extensions
2500            .account_data
2501            .global
2502            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2503        client
2504            .process_sliding_sync(
2505                &response,
2506                &RequestedRequiredStates::default(),
2507                &client.state_store_lock().lock().await,
2508            )
2509            .await
2510            .expect("Failed to process sync");
2511
2512        let room_1 = client.get_room(room_id_1).unwrap();
2513        assert!(room_1.is_direct().await.unwrap());
2514
2515        // Now perform a sync without new account data
2516        let mut room_response = http::response::Room::new();
2517        set_room_joined(&mut room_response, user_b_id);
2518        let response = response_with_room(room_id_2, room_response);
2519        client
2520            .process_sliding_sync(
2521                &response,
2522                &RequestedRequiredStates::default(),
2523                &client.state_store_lock().lock().await,
2524            )
2525            .await
2526            .expect("Failed to process sync");
2527
2528        let room_2 = client.get_room(room_id_2).unwrap();
2529        assert!(room_2.is_direct().await.unwrap());
2530    }
2531
2532    #[async_test]
2533    async fn test_room_encryption_state_is_and_is_not_encrypted() {
2534        let user_id = user_id!("@raclette:patate");
2535        let client = logged_in_base_client(Some(user_id)).await;
2536        let room_id_0 = room_id!("!r0");
2537        let room_id_1 = room_id!("!r1");
2538        let room_id_2 = room_id!("!r2");
2539
2540        // A room is considered encrypted when it receives a `m.room.encryption` event,
2541        // period.
2542        //
2543        // A room is considered **not** encrypted when it receives no
2544        // `m.room.encryption` event but it was requested, period.
2545        //
2546        // We are going to test three rooms:
2547        //
2548        // - two of them receive a `m.room.encryption` event
2549        // - the last one does not receive a `m.room.encryption`.
2550        // - the first one is configured with a `required_state` for this event, the
2551        //   others have nothing.
2552        //
2553        // The trick is that, since sliding sync makes an union of all the
2554        // `required_state`s, then all rooms are technically requesting a
2555        // `m.room.encryption`.
2556        let requested_required_states = RequestedRequiredStates::from(&{
2557            let mut request = http::Request::new();
2558
2559            request.room_subscriptions.insert(room_id_0.to_owned(), {
2560                let mut room_subscription = http::request::RoomSubscription::default();
2561
2562                room_subscription
2563                    .required_state
2564                    .push((StateEventType::RoomEncryption, "".to_owned()));
2565
2566                room_subscription
2567            });
2568
2569            request
2570        });
2571
2572        let mut response = http::Response::new("0".to_owned());
2573
2574        // Create two rooms that are encrypted, i.e. they have a `m.room.encryption`
2575        // state event in their `required_state`. Create a third room that is not
2576        // encrypted, i.e. it doesn't have a `m.room.encryption` state event.
2577        {
2578            let not_encrypted_room = http::response::Room::new();
2579            let mut encrypted_room = http::response::Room::new();
2580            set_room_is_encrypted(&mut encrypted_room, user_id);
2581
2582            response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2583            response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2584            response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2585        }
2586
2587        client
2588            .process_sliding_sync(
2589                &response,
2590                &requested_required_states,
2591                &client.state_store_lock().lock().await,
2592            )
2593            .await
2594            .expect("Failed to process sync");
2595
2596        // They are both encrypted, yepee.
2597        assert_matches!(
2598            client.get_room(room_id_0).unwrap().encryption_state(),
2599            EncryptionState::Encrypted
2600        );
2601        assert_matches!(
2602            client.get_room(room_id_1).unwrap().encryption_state(),
2603            EncryptionState::Encrypted
2604        );
2605        // This one is not encrypted because it has received nothing.
2606        assert_matches!(
2607            client.get_room(room_id_2).unwrap().encryption_state(),
2608            EncryptionState::NotEncrypted
2609        )
2610    }
2611
2612    #[async_test]
2613    async fn test_room_encryption_state_is_unknown() {
2614        let user_id = user_id!("@raclette:patate");
2615        let client = logged_in_base_client(Some(user_id)).await;
2616        let room_id_0 = room_id!("!r0");
2617        let room_id_1 = room_id!("!r1");
2618
2619        // A room is considered encrypted when it receives a `m.room.encryption` event,
2620        // period.
2621        //
2622        // A room is considered **not** encrypted when it receives no
2623        // `m.room.encryption` event but it was requested, period.
2624        //
2625        // We are going to test two rooms:
2626        //
2627        // - one that receives a `m.room.encryption` event,
2628        // - one that receives nothing,
2629        // - none of them have requested the state event.
2630
2631        let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2632
2633        let mut response = http::Response::new("0".to_owned());
2634
2635        // Create two rooms with and without a `m.room.encryption` event.
2636        {
2637            let not_encrypted_room = http::response::Room::new();
2638            let mut encrypted_room = http::response::Room::new();
2639            set_room_is_encrypted(&mut encrypted_room, user_id);
2640
2641            response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2642            response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2643        }
2644
2645        client
2646            .process_sliding_sync(
2647                &response,
2648                &requested_required_states,
2649                &client.state_store_lock().lock().await,
2650            )
2651            .await
2652            .expect("Failed to process sync");
2653
2654        // Encrypted, because the presence of a `m.room.encryption` always mean the room
2655        // is encrypted.
2656        assert_matches!(
2657            client.get_room(room_id_0).unwrap().encryption_state(),
2658            EncryptionState::Encrypted
2659        );
2660        // Unknown, because the absence of `m.room.encryption` when not requested
2661        // means we don't know what the state is.
2662        assert_matches!(
2663            client.get_room(room_id_1).unwrap().encryption_state(),
2664            EncryptionState::Unknown
2665        );
2666    }
2667
2668    async fn membership(
2669        client: &BaseClient,
2670        room_id: &RoomId,
2671        user_id: &UserId,
2672    ) -> MembershipState {
2673        let room = client.get_room(room_id).expect("Room not found!");
2674        let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2675        member.membership().clone()
2676    }
2677
2678    fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2679        let room = client.get_room(room_id).expect("Room not found!");
2680        room.direct_targets()
2681    }
2682
2683    /// Create a DM with the other user, setting our membership to Join and
2684    /// theirs to other_state
2685    async fn create_dm(
2686        client: &BaseClient,
2687        room_id: &RoomId,
2688        my_id: &UserId,
2689        their_id: &UserId,
2690        other_state: MembershipState,
2691    ) {
2692        let mut room = http::response::Room::new();
2693        set_room_joined(&mut room, my_id);
2694
2695        match other_state {
2696            MembershipState::Join => {
2697                room.joined_count = Some(uint!(2));
2698                room.invited_count = None;
2699            }
2700
2701            MembershipState::Invite => {
2702                room.joined_count = Some(uint!(1));
2703                room.invited_count = Some(uint!(1));
2704            }
2705
2706            _ => {
2707                room.joined_count = Some(uint!(1));
2708                room.invited_count = None;
2709            }
2710        }
2711
2712        room.required_state.push(make_membership_event(their_id, other_state));
2713
2714        let mut response = response_with_room(room_id, room);
2715        set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2716        client
2717            .process_sliding_sync(
2718                &response,
2719                &RequestedRequiredStates::default(),
2720                &client.state_store_lock().lock().await,
2721            )
2722            .await
2723            .expect("Failed to process sync");
2724    }
2725
2726    /// Set this user's membership within this room to new_state
2727    async fn update_room_membership(
2728        client: &BaseClient,
2729        room_id: &RoomId,
2730        user_id: &UserId,
2731        new_state: MembershipState,
2732    ) {
2733        let mut room = http::response::Room::new();
2734        room.required_state.push(make_membership_event(user_id, new_state));
2735        let response = response_with_room(room_id, room);
2736        client
2737            .process_sliding_sync(
2738                &response,
2739                &RequestedRequiredStates::default(),
2740                &client.state_store_lock().lock().await,
2741            )
2742            .await
2743            .expect("Failed to process sync");
2744    }
2745
2746    fn set_direct_with(
2747        response: &mut http::Response,
2748        user_id: OwnedUserId,
2749        room_ids: Vec<OwnedRoomId>,
2750    ) {
2751        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2752            BTreeMap::new();
2753        direct_content.insert(user_id.into(), room_ids);
2754        response
2755            .extensions
2756            .account_data
2757            .global
2758            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2759    }
2760
2761    fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2762        let mut response = http::Response::new("5".to_owned());
2763        response.rooms.insert(room_id.to_owned(), room);
2764        response
2765    }
2766
2767    fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2768        let mut room = http::response::Room::new();
2769
2770        let mut avatar_event_content = RoomAvatarEventContent::new();
2771        avatar_event_content.url = Some(avatar_uri.to_owned());
2772
2773        room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2774
2775        room
2776    }
2777
2778    fn room_with_canonical_alias(
2779        room_alias_id: &RoomAliasId,
2780        user_id: &UserId,
2781    ) -> http::response::Room {
2782        let mut room = http::response::Room::new();
2783
2784        let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2785        canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2786
2787        room.required_state.push(make_state_event(
2788            user_id,
2789            "",
2790            canonical_alias_event_content,
2791            None,
2792        ));
2793
2794        room
2795    }
2796
2797    fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2798        let mut room = http::response::Room::new();
2799
2800        let name_event_content = RoomNameEventContent::new(name.to_owned());
2801
2802        room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2803
2804        room
2805    }
2806
2807    fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2808        room.required_state.push(make_state_event(
2809            sender,
2810            "",
2811            RoomNameEventContent::new(name),
2812            None,
2813        ));
2814    }
2815
2816    fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2817        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2818        // room. Just the type is supplied.
2819
2820        let evt = Raw::new(&json!({
2821            "type": "m.room.member",
2822            "sender": inviter,
2823            "content": {
2824                "is_direct": true,
2825                "membership": "invite",
2826            },
2827            "state_key": invitee,
2828        }))
2829        .expect("Failed to make raw event")
2830        .cast_unchecked();
2831
2832        room.invite_state = Some(vec![evt]);
2833
2834        // We expect that there will also be an invite event in the required_state,
2835        // assuming you've asked for this type of event.
2836        room.required_state.push(make_state_event(
2837            inviter,
2838            invitee.as_str(),
2839            RoomMemberEventContent::new(MembershipState::Invite),
2840            None,
2841        ));
2842    }
2843
2844    fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2845        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2846        // room. Just the type is supplied.
2847
2848        let evt = Raw::new(&json!({
2849            "type": "m.room.member",
2850            "sender": knocker,
2851            "content": {
2852                "is_direct": true,
2853                "membership": "knock",
2854            },
2855            "state_key": knocker,
2856        }))
2857        .expect("Failed to make raw event")
2858        .cast_unchecked();
2859
2860        room.invite_state = Some(vec![evt]);
2861    }
2862
2863    fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2864        room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2865    }
2866
2867    fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2868        room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2869    }
2870
2871    fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2872        room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2873    }
2874
2875    fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2876        room.required_state.push(make_encryption_event(user_id));
2877    }
2878
2879    fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2880        make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2881    }
2882
2883    fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2884        make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2885    }
2886
2887    fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2888        Raw::new(&json!({
2889            "type": content.event_type(),
2890            "content": content,
2891        }))
2892        .expect("Failed to create account data event")
2893        .cast_unchecked()
2894    }
2895
2896    fn make_state_event<C: StateEventContent, E>(
2897        sender: &UserId,
2898        state_key: &str,
2899        content: C,
2900        prev_content: Option<C>,
2901    ) -> Raw<E> {
2902        let unsigned = if let Some(prev_content) = prev_content {
2903            json!({ "prev_content": prev_content })
2904        } else {
2905            json!({})
2906        };
2907
2908        Raw::new(&json!({
2909            "type": content.event_type(),
2910            "state_key": state_key,
2911            "content": content,
2912            "event_id": event_id!("$evt"),
2913            "sender": sender,
2914            "origin_server_ts": 10,
2915            "unsigned": unsigned,
2916        }))
2917        .expect("Failed to create state event")
2918        .cast_unchecked()
2919    }
2920
2921    fn make_profile_update(field: ProfileFieldName, value: serde_json::Value) -> UserProfileUpdate {
2922        let mut changes = UserProfileChanges::new();
2923        changes.updated.insert(field, value);
2924        UserProfileUpdate::Updated(changes)
2925    }
2926}