Skip to main content

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