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