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