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