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