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