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