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