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