matrix_sdk_base/
sliding_sync.rs

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