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