1use std::collections::BTreeMap;
18#[cfg(feature = "e2e-encryption")]
19use std::ops::Deref;
20
21#[cfg(feature = "e2e-encryption")]
22use matrix_sdk_common::deserialized_responses::TimelineEvent;
23use ruma::{
24 api::client::sync::sync_events::{
25 v3::{self, InvitedRoom, KnockedRoom},
26 v5 as http,
27 },
28 events::{
29 room::member::MembershipState, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
30 AnySyncStateEvent,
31 },
32 serde::Raw,
33 JsOption, OwnedRoomId, RoomId, UserId,
34};
35#[cfg(feature = "e2e-encryption")]
36use ruma::{events::AnyToDeviceEvent, events::StateEventType};
37use tracing::{instrument, trace, warn};
38
39use super::BaseClient;
40use crate::{
41 error::Result,
42 read_receipts::{compute_unread_counts, PreviousEventsProvider},
43 response_processors::AccountDataProcessor,
44 rooms::{
45 normal::{RoomHero, RoomInfoNotableUpdateReasons},
46 RoomState,
47 },
48 ruma::assign,
49 store::{ambiguity_map::AmbiguityCache, StateChanges, Store},
50 sync::{JoinedRoomUpdate, LeftRoomUpdate, Notification, RoomUpdates, SyncResponse},
51 Room, RoomInfo,
52};
53#[cfg(feature = "e2e-encryption")]
54use crate::{
55 latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
56 RoomMemberships,
57};
58
59impl BaseClient {
60 #[cfg(feature = "e2e-encryption")]
61 pub async fn process_sliding_sync_e2ee(
69 &self,
70 to_device: Option<&http::response::ToDevice>,
71 e2ee: &http::response::E2EE,
72 ) -> Result<Option<Vec<Raw<AnyToDeviceEvent>>>> {
73 if to_device.is_none() && e2ee.is_empty() {
74 return Ok(None);
75 }
76
77 let to_device_events =
78 to_device.as_ref().map(|to_device| to_device.events.clone()).unwrap_or_default();
79
80 trace!(
81 to_device_events = to_device_events.len(),
82 device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
83 device_unused_fallback_key_types =
84 e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
85 "Processing sliding sync e2ee events",
86 );
87
88 let mut changes = StateChanges::default();
89 let mut room_info_notable_updates =
90 BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
91
92 let to_device = self
96 .preprocess_to_device_events(
97 matrix_sdk_crypto::EncryptionSyncChanges {
98 to_device_events,
99 changed_devices: &e2ee.device_lists,
100 one_time_keys_counts: &e2ee.device_one_time_keys_count,
101 unused_fallback_keys: e2ee.device_unused_fallback_key_types.as_deref(),
102 next_batch_token: to_device
103 .as_ref()
104 .map(|to_device| to_device.next_batch.clone()),
105 },
106 &mut changes,
107 &mut room_info_notable_updates,
108 )
109 .await?;
110
111 trace!("ready to submit e2ee changes to store");
112 self.store.save_changes(&changes).await?;
113 self.apply_changes(&changes, room_info_notable_updates);
114 trace!("applied e2ee changes");
115
116 Ok(Some(to_device))
117 }
118
119 #[instrument(skip_all, level = "trace")]
128 pub async fn process_sliding_sync<PEP: PreviousEventsProvider>(
129 &self,
130 response: &http::Response,
131 previous_events_provider: &PEP,
132 ) -> Result<SyncResponse> {
133 let http::Response {
134 rooms,
138 lists,
139 extensions,
140 ..
143 } = response;
144
145 trace!(
146 rooms = rooms.len(),
147 lists = lists.len(),
148 has_extensions = !extensions.is_empty(),
149 "Processing sliding sync room events"
150 );
151
152 if rooms.is_empty() && extensions.is_empty() {
153 return Ok(SyncResponse::default());
156 };
157
158 let mut changes = StateChanges::default();
159 let mut room_info_notable_updates =
160 BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
161
162 let store = self.store.clone();
163 let mut ambiguity_cache = AmbiguityCache::new(store.inner.clone());
164
165 let account_data_processor = AccountDataProcessor::process(&extensions.account_data.global);
166
167 let mut new_rooms = RoomUpdates::default();
168 let mut notifications = Default::default();
169 let mut rooms_account_data = extensions.account_data.rooms.clone();
170
171 let user_id = self
172 .session_meta()
173 .expect("Sliding sync shouldn't run without an authenticated user.")
174 .user_id
175 .to_owned();
176
177 for (room_id, response_room_data) in rooms {
178 let (room_info, joined_room, left_room, invited_room, knocked_room) = self
179 .process_sliding_sync_room(
180 room_id,
181 response_room_data,
182 &mut rooms_account_data,
183 &store,
184 &user_id,
185 &account_data_processor,
186 &mut changes,
187 &mut room_info_notable_updates,
188 &mut notifications,
189 &mut ambiguity_cache,
190 )
191 .await?;
192
193 changes.add_room(room_info);
194
195 if let Some(joined_room) = joined_room {
196 new_rooms.join.insert(room_id.clone(), joined_room);
197 }
198
199 if let Some(left_room) = left_room {
200 new_rooms.leave.insert(room_id.clone(), left_room);
201 }
202
203 if let Some(invited_room) = invited_room {
204 new_rooms.invite.insert(room_id.clone(), invited_room);
205 }
206
207 if let Some(knocked_room) = knocked_room {
208 new_rooms.knocked.insert(room_id.clone(), knocked_room);
209 }
210 }
211
212 for (room_id, raw) in &extensions.receipts.rooms {
217 match raw.deserialize() {
218 Ok(event) => {
219 changes.add_receipts(room_id, event.content);
220 }
221 Err(e) => {
222 let event_id: Option<String> = raw.get_field("event_id").ok().flatten();
223 #[rustfmt::skip]
224 warn!(
225 ?room_id, event_id,
226 "Failed to deserialize read receipt room event: {e}"
227 );
228 }
229 }
230
231 new_rooms
233 .join
234 .entry(room_id.to_owned())
235 .or_insert_with(JoinedRoomUpdate::default)
236 .ephemeral
237 .push(raw.clone().cast());
238 }
239
240 for (room_id, raw) in &extensions.typing.rooms {
241 new_rooms
243 .join
244 .entry(room_id.to_owned())
245 .or_insert_with(JoinedRoomUpdate::default)
246 .ephemeral
247 .push(raw.clone().cast());
248 }
249
250 for (room_id, raw) in &rooms_account_data {
252 self.handle_room_account_data(
253 room_id,
254 raw,
255 &mut changes,
256 &mut room_info_notable_updates,
257 )
258 .await;
259
260 if let Some(room) = self.store.room(room_id) {
261 match room.state() {
262 RoomState::Joined => new_rooms
263 .join
264 .entry(room_id.to_owned())
265 .or_insert_with(JoinedRoomUpdate::default)
266 .account_data
267 .append(&mut raw.to_vec()),
268 RoomState::Left | RoomState::Banned => new_rooms
269 .leave
270 .entry(room_id.to_owned())
271 .or_insert_with(LeftRoomUpdate::default)
272 .account_data
273 .append(&mut raw.to_vec()),
274 RoomState::Invited | RoomState::Knocked => {}
275 }
276 }
277 }
278
279 let user_id = &self.session_meta().expect("logged in user").user_id;
282
283 for (room_id, joined_room_update) in &mut new_rooms.join {
284 if let Some(mut room_info) = changes
285 .room_infos
286 .get(room_id)
287 .cloned()
288 .or_else(|| self.get_room(room_id).map(|r| r.clone_info()))
289 {
290 let prev_read_receipts = room_info.read_receipts.clone();
291
292 compute_unread_counts(
293 user_id,
294 room_id,
295 changes.receipts.get(room_id),
296 previous_events_provider.for_room(room_id),
297 &joined_room_update.timeline.events,
298 &mut room_info.read_receipts,
299 );
300
301 if prev_read_receipts != room_info.read_receipts {
302 room_info_notable_updates
303 .entry(room_id.clone())
304 .or_default()
305 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
306
307 changes.add_room(room_info);
308 }
309 }
310 }
311
312 account_data_processor.apply(&mut changes, &store).await;
313
314 changes.ambiguity_maps = ambiguity_cache.cache;
325
326 trace!("ready to submit changes to store");
327 store.save_changes(&changes).await?;
328 self.apply_changes(&changes, room_info_notable_updates);
329 trace!("applied changes");
330
331 new_rooms.update_in_memory_caches(&self.store).await;
337
338 Ok(SyncResponse {
339 rooms: new_rooms,
340 notifications,
341 presence: Default::default(),
343 account_data: extensions.account_data.global.clone(),
344 to_device: Default::default(),
345 })
346 }
347
348 #[allow(clippy::too_many_arguments)]
349 async fn process_sliding_sync_room(
350 &self,
351 room_id: &RoomId,
352 room_data: &http::response::Room,
353 rooms_account_data: &mut BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
354 store: &Store,
355 user_id: &UserId,
356 account_data_processor: &AccountDataProcessor,
357 changes: &mut StateChanges,
358 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
359 notifications: &mut BTreeMap<OwnedRoomId, Vec<Notification>>,
360 ambiguity_cache: &mut AmbiguityCache,
361 ) -> Result<(
362 RoomInfo,
363 Option<JoinedRoomUpdate>,
364 Option<LeftRoomUpdate>,
365 Option<InvitedRoom>,
366 Option<KnockedRoom>,
367 )> {
368 let (raw_state_events, state_events): (Vec<_>, Vec<_>) = {
369 let state_events = Self::deserialize_state_events(&room_data.required_state);
371
372 state_events.into_iter().unzip()
377 };
378
379 let is_new_room = !store.room_exists(room_id);
381
382 let stripped_state: Option<Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>> =
383 room_data
384 .invite_state
385 .as_ref()
386 .map(|invite_state| Self::deserialize_stripped_state_events(invite_state));
387
388 #[allow(unused_mut)] let (mut room, mut room_info, invited_room, knocked_room) = self
390 .process_sliding_sync_room_membership(
391 &state_events,
392 stripped_state.as_ref(),
393 store,
394 user_id,
395 room_id,
396 room_info_notable_updates,
397 );
398
399 room_info.mark_state_partially_synced();
400
401 let mut user_ids = if !state_events.is_empty() {
402 self.handle_state(
403 &raw_state_events,
404 &state_events,
405 &mut room_info,
406 changes,
407 ambiguity_cache,
408 )
409 .await?
410 } else {
411 Default::default()
412 };
413
414 let push_rules = self.get_push_rules(account_data_processor).await?;
415
416 if let Some(invite_state) = &stripped_state {
418 self.handle_invited_state(
419 &room,
420 invite_state,
421 &push_rules,
422 &mut room_info,
423 changes,
424 notifications,
425 )
426 .await?;
427 }
428
429 process_room_properties(
430 room_id,
431 room_data,
432 &mut room_info,
433 is_new_room,
434 room_info_notable_updates,
435 );
436
437 let timeline = self
438 .handle_timeline(
439 &room,
440 room_data.limited,
441 room_data.timeline.clone(),
442 true,
443 room_data.prev_batch.clone(),
444 &push_rules,
445 &mut user_ids,
446 &mut room_info,
447 changes,
448 notifications,
449 ambiguity_cache,
450 )
451 .await?;
452
453 #[cfg(feature = "e2e-encryption")]
456 cache_latest_events(&room, &mut room_info, &timeline.events, Some(changes), Some(store))
457 .await;
458
459 #[cfg(feature = "e2e-encryption")]
460 if room_info.is_encrypted() {
461 if let Some(o) = self.olm_machine().await.as_ref() {
462 if !room.is_encrypted() {
463 let user_ids = store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
467 o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
468 }
469
470 if !user_ids.is_empty() {
471 o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?;
472 }
473 }
474 }
475
476 let notification_count = room_data.unread_notifications.clone().into();
477 room_info.update_notification_count(notification_count);
478
479 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
480 let room_account_data = rooms_account_data.get(room_id).cloned();
481
482 match room_info.state() {
483 RoomState::Joined => {
484 let ephemeral = Vec::new();
488
489 Ok((
490 room_info,
491 Some(JoinedRoomUpdate::new(
492 timeline,
493 raw_state_events,
494 room_account_data.unwrap_or_default(),
495 ephemeral,
496 notification_count,
497 ambiguity_changes,
498 )),
499 None,
500 None,
501 None,
502 ))
503 }
504
505 RoomState::Left | RoomState::Banned => Ok((
506 room_info,
507 None,
508 Some(LeftRoomUpdate::new(
509 timeline,
510 raw_state_events,
511 room_account_data.unwrap_or_default(),
512 ambiguity_changes,
513 )),
514 None,
515 None,
516 )),
517
518 RoomState::Invited => Ok((room_info, None, None, invited_room, None)),
519
520 RoomState::Knocked => Ok((room_info, None, None, None, knocked_room)),
521 }
522 }
523
524 fn process_sliding_sync_room_membership(
530 &self,
531 state_events: &[AnySyncStateEvent],
532 stripped_state: Option<&Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>>,
533 store: &Store,
534 user_id: &UserId,
535 room_id: &RoomId,
536 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
537 ) -> (Room, RoomInfo, Option<InvitedRoom>, Option<KnockedRoom>) {
538 if let Some(stripped_state) = stripped_state {
539 let room = store.get_or_create_room(
540 room_id,
541 RoomState::Invited,
542 self.room_info_notable_update_sender.clone(),
543 );
544 let mut room_info = room.clone_info();
545
546 let membership_event_content = stripped_state.iter().find_map(|(_, event)| {
549 if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
550 if membership_event.state_key == user_id {
551 return Some(membership_event.content.clone());
552 }
553 }
554 None
555 });
556
557 if let Some(membership_event_content) = membership_event_content {
558 if membership_event_content.membership == MembershipState::Knock {
559 room_info.mark_as_knocked();
561 let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect();
562 let knock_state = assign!(v3::KnockState::default(), { events: raw_events });
563 let knocked_room =
564 assign!(KnockedRoom::default(), { knock_state: knock_state });
565 return (room, room_info, None, Some(knocked_room));
566 }
567 }
568
569 room_info.mark_as_invited();
571 let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect::<Vec<_>>();
572 let invited_room = InvitedRoom::from(v3::InviteState::from(raw_events));
573 (room, room_info, Some(invited_room), None)
574 } else {
575 let room = store.get_or_create_room(
576 room_id,
577 RoomState::Joined,
578 self.room_info_notable_update_sender.clone(),
579 );
580 let mut room_info = room.clone_info();
581
582 room_info.mark_as_joined();
587
588 self.handle_own_room_membership(
594 state_events,
595 &mut room_info,
596 room_info_notable_updates,
597 );
598
599 (room, room_info, None, None)
600 }
601 }
602
603 pub(crate) fn handle_own_room_membership(
606 &self,
607 state_events: &[AnySyncStateEvent],
608 room_info: &mut RoomInfo,
609 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
610 ) {
611 let Some(meta) = self.session_meta() else {
612 return;
613 };
614
615 for event in state_events.iter().rev() {
619 if let AnySyncStateEvent::RoomMember(member) = &event {
620 if member.state_key() == meta.user_id.as_str() {
623 let new_state: RoomState = member.membership().into();
624 if new_state != room_info.state() {
625 room_info.set_state(new_state);
626 room_info_notable_updates
628 .entry(room_info.room_id.to_owned())
629 .or_default()
630 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
631 }
632 break;
633 }
634 }
635 }
636 }
637}
638
639#[cfg(feature = "e2e-encryption")]
647async fn cache_latest_events(
648 room: &Room,
649 room_info: &mut RoomInfo,
650 events: &[TimelineEvent],
651 changes: Option<&StateChanges>,
652 store: Option<&Store>,
653) {
654 use crate::{
655 deserialized_responses::DisplayName, store::ambiguity_map::is_display_name_ambiguous,
656 };
657
658 let mut encrypted_events =
659 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
660
661 let power_levels_from_changes = || {
663 let state_changes = changes?.state.get(room_info.room_id())?;
664 let room_power_levels_state =
665 state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
666 match room_power_levels_state.deserialize().ok()? {
667 AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
668 _ => None,
669 }
670 };
671
672 let power_levels = match power_levels_from_changes() {
674 Some(power_levels) => Some(power_levels),
675 None => room.power_levels().await.ok(),
676 };
677
678 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
679
680 for event in events.iter().rev() {
681 if let Ok(timeline_event) = event.raw().deserialize() {
682 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
683 PossibleLatestEvent::YesRoomMessage(_)
684 | PossibleLatestEvent::YesPoll(_)
685 | PossibleLatestEvent::YesCallInvite(_)
686 | PossibleLatestEvent::YesCallNotify(_)
687 | PossibleLatestEvent::YesSticker(_)
688 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
689 let mut sender_profile = None;
697 let mut sender_name_is_ambiguous = None;
698
699 if let Some(changes) = changes {
702 sender_profile = changes
703 .profiles
704 .get(room.room_id())
705 .and_then(|profiles_by_user| {
706 profiles_by_user.get(timeline_event.sender())
707 })
708 .cloned();
709
710 if let Some(sender_profile) = sender_profile.as_ref() {
711 sender_name_is_ambiguous = sender_profile
712 .as_original()
713 .and_then(|profile| profile.content.displayname.as_ref())
714 .and_then(|display_name| {
715 let display_name = DisplayName::new(display_name);
716
717 changes.ambiguity_maps.get(room.room_id()).and_then(
718 |map_for_room| {
719 map_for_room.get(&display_name).map(|users| {
720 is_display_name_ambiguous(&display_name, users)
721 })
722 },
723 )
724 });
725 }
726 }
727
728 if sender_profile.is_none() {
730 if let Some(store) = store {
731 sender_profile = store
732 .get_profile(room.room_id(), timeline_event.sender())
733 .await
734 .ok()
735 .flatten();
736
737 }
740 }
741
742 let latest_event = Box::new(LatestEvent::new_with_sender_details(
743 event.clone(),
744 sender_profile,
745 sender_name_is_ambiguous,
746 ));
747
748 room_info.latest_event = Some(latest_event);
750 room.latest_encrypted_events.write().unwrap().clear();
753 break;
756 }
757 PossibleLatestEvent::NoEncrypted => {
758 if encrypted_events.len() < encrypted_events.capacity() {
764 encrypted_events.push(event.raw().clone());
765 }
766 }
767 _ => {
768 }
770 }
771 } else {
772 warn!(
773 "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
774 event.event_id().expect("Event has no ID!")
775 );
776 }
777 }
778
779 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
782}
783
784fn process_room_properties(
785 room_id: &RoomId,
786 room_data: &http::response::Room,
787 room_info: &mut RoomInfo,
788 is_new_room: bool,
789 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
790) {
791 match &room_data.avatar {
797 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
799 JsOption::Null => room_info.update_avatar(None),
801 JsOption::Undefined => {}
803 }
804
805 if let Some(count) = room_data.joined_count {
809 room_info.update_joined_member_count(count.into());
810 }
811 if let Some(count) = room_data.invited_count {
812 room_info.update_invited_member_count(count.into());
813 }
814
815 if let Some(heroes) = &room_data.heroes {
816 room_info.update_heroes(
817 heroes
818 .iter()
819 .map(|hero| RoomHero {
820 user_id: hero.user_id.clone(),
821 display_name: hero.name.clone(),
822 avatar_url: hero.avatar.clone(),
823 })
824 .collect(),
825 );
826 }
827
828 room_info.set_prev_batch(room_data.prev_batch.as_deref());
829
830 if room_data.limited {
831 room_info.mark_members_missing();
832 }
833
834 if let Some(recency_stamp) = &room_data.bump_stamp {
835 let recency_stamp: u64 = (*recency_stamp).into();
836
837 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
838 room_info.update_recency_stamp(recency_stamp);
839
840 if !is_new_room {
844 room_info_notable_updates
845 .entry(room_id.to_owned())
846 .or_default()
847 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
848 }
849 }
850 }
851}
852
853#[cfg(all(test, not(target_family = "wasm")))]
854mod tests {
855 use std::collections::{BTreeMap, HashSet};
856 #[cfg(feature = "e2e-encryption")]
857 use std::sync::{Arc, RwLock as SyncRwLock};
858
859 use assert_matches::assert_matches;
860 use matrix_sdk_common::deserialized_responses::TimelineEvent;
861 #[cfg(feature = "e2e-encryption")]
862 use matrix_sdk_common::{
863 deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
864 ring_buffer::RingBuffer,
865 };
866 use matrix_sdk_test::async_test;
867 use ruma::{
868 api::client::sync::sync_events::UnreadNotificationsCount,
869 assign, event_id,
870 events::{
871 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
872 room::{
873 avatar::RoomAvatarEventContent,
874 canonical_alias::RoomCanonicalAliasEventContent,
875 member::{MembershipState, RoomMemberEventContent},
876 message::SyncRoomMessageEvent,
877 name::RoomNameEventContent,
878 pinned_events::RoomPinnedEventsEventContent,
879 },
880 AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
881 StateEventContent,
882 },
883 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
884 serde::Raw,
885 uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
886 };
887 use serde_json::json;
888
889 #[cfg(feature = "e2e-encryption")]
890 use super::cache_latest_events;
891 use super::http;
892 use crate::{
893 rooms::normal::{RoomHero, RoomInfoNotableUpdateReasons},
894 test_utils::logged_in_base_client,
895 BaseClient, RoomInfoNotableUpdate, RoomState,
896 };
897 #[cfg(feature = "e2e-encryption")]
898 use crate::{store::MemoryStore, Room};
899
900 #[async_test]
901 async fn test_notification_count_set() {
902 let client = logged_in_base_client(None).await;
903
904 let mut response = http::Response::new("42".to_owned());
905 let room_id = room_id!("!room:example.org");
906 let count = assign!(UnreadNotificationsCount::default(), {
907 highlight_count: Some(uint!(13)),
908 notification_count: Some(uint!(37)),
909 });
910
911 response.rooms.insert(
912 room_id.to_owned(),
913 assign!(http::response::Room::new(), {
914 unread_notifications: count.clone()
915 }),
916 );
917
918 let sync_response =
919 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
920
921 let room = sync_response.rooms.join.get(room_id).unwrap();
923 assert_eq!(room.unread_notifications, count.clone().into());
924
925 let room = client.get_room(room_id).expect("found room");
927 assert_eq!(room.unread_notification_counts(), count.into());
928 }
929
930 #[async_test]
931 async fn test_can_process_empty_sliding_sync_response() {
932 let client = logged_in_base_client(None).await;
933 let empty_response = http::Response::new("5".to_owned());
934 client.process_sliding_sync(&empty_response, &()).await.expect("Failed to process sync");
935 }
936
937 #[async_test]
938 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
939 let client = logged_in_base_client(None).await;
941 let room_id = room_id!("!r:e.uk");
942
943 let mut room = http::response::Room::new();
946 room.joined_count = Some(uint!(41));
947 let response = response_with_room(room_id, room);
948 let sync_resp =
949 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
950
951 let client_room = client.get_room(room_id).expect("No room found");
953 assert_eq!(client_room.room_id(), room_id);
954 assert_eq!(client_room.joined_members_count(), 41);
955 assert_eq!(client_room.state(), RoomState::Joined);
956
957 assert!(sync_resp.rooms.join.contains_key(room_id));
959 assert!(!sync_resp.rooms.leave.contains_key(room_id));
960 assert!(!sync_resp.rooms.invite.contains_key(room_id));
961 }
962
963 #[async_test]
964 async fn test_missing_room_name_event() {
965 let client = logged_in_base_client(None).await;
967 let room_id = room_id!("!r:e.uk");
968
969 let mut room = http::response::Room::new();
972 room.name = Some("little room".to_owned());
973 let response = response_with_room(room_id, room);
974 let sync_resp =
975 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
976
977 let client_room = client.get_room(room_id).expect("No room found");
979 assert!(client_room.name().is_none());
980 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "Empty Room");
981 assert_eq!(client_room.state(), RoomState::Joined);
982
983 assert!(sync_resp.rooms.join.contains_key(room_id));
985 assert!(!sync_resp.rooms.leave.contains_key(room_id));
986 assert!(!sync_resp.rooms.invite.contains_key(room_id));
987 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
988 }
989
990 #[async_test]
991 async fn test_room_name_event() {
992 let client = logged_in_base_client(None).await;
994 let room_id = room_id!("!r:e.uk");
995
996 let mut room = http::response::Room::new();
999
1000 room.name = Some("little room".to_owned());
1001 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1002
1003 let response = response_with_room(room_id, room);
1004 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1005
1006 let client_room = client.get_room(room_id).expect("No room found");
1008 assert_eq!(client_room.name().as_deref(), Some("The Name"));
1009 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1010 }
1011
1012 #[async_test]
1013 async fn test_missing_invited_room_name_event() {
1014 let client = logged_in_base_client(None).await;
1016 let room_id = room_id!("!r:e.uk");
1017 let user_id = user_id!("@w:e.uk");
1018 let inviter = user_id!("@john:mastodon.org");
1019
1020 let mut room = http::response::Room::new();
1023 set_room_invited(&mut room, inviter, user_id);
1024 room.name = Some("name from sliding sync response".to_owned());
1025 let response = response_with_room(room_id, room);
1026 let sync_resp =
1027 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1028
1029 let client_room = client.get_room(room_id).expect("No room found");
1031 assert!(client_room.name().is_none());
1032
1033 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "w");
1035
1036 assert_eq!(client_room.state(), RoomState::Invited);
1037
1038 assert!(!sync_resp.rooms.join.contains_key(room_id));
1040 assert!(!sync_resp.rooms.leave.contains_key(room_id));
1041 assert!(sync_resp.rooms.invite.contains_key(room_id));
1042 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1043 }
1044
1045 #[async_test]
1046 async fn test_invited_room_name_event() {
1047 let client = logged_in_base_client(None).await;
1049 let room_id = room_id!("!r:e.uk");
1050 let user_id = user_id!("@w:e.uk");
1051 let inviter = user_id!("@john:mastodon.org");
1052
1053 let mut room = http::response::Room::new();
1056
1057 set_room_invited(&mut room, inviter, user_id);
1058
1059 room.name = Some("name from sliding sync response".to_owned());
1060 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1061
1062 let response = response_with_room(room_id, room);
1063 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1064
1065 let client_room = client.get_room(room_id).expect("No room found");
1067 assert_eq!(client_room.name().as_deref(), Some("The Name"));
1068 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1069 }
1070
1071 #[async_test]
1072 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
1073 let client = logged_in_base_client(None).await;
1075 let room_id = room_id!("!r:e.uk");
1076 let user_id = client.session_meta().unwrap().user_id.to_owned();
1077
1078 let mut room = http::response::Room::new();
1081 set_room_knocked(&mut room, &user_id);
1082
1083 let response = response_with_room(room_id, room);
1084 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1085
1086 let client_room = client.get_room(room_id).expect("No room found");
1088 assert_eq!(client_room.state(), RoomState::Knocked);
1089 }
1090
1091 #[async_test]
1092 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
1093 ) {
1094 let client = logged_in_base_client(None).await;
1096 let room_id = room_id!("!r:e.uk");
1097 let user_id = user_id!("@w:e.uk");
1098
1099 let mut room = http::response::Room::new();
1101 set_room_knocked(&mut room, user_id);
1102
1103 let response = response_with_room(room_id, room);
1104 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1105
1106 let client_room = client.get_room(room_id).expect("No room found");
1109 assert_eq!(client_room.state(), RoomState::Invited);
1110 }
1111
1112 #[async_test]
1113 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
1114 ) {
1115 let client = logged_in_base_client(None).await;
1117 let room_id = room_id!("!r:e.uk");
1118 let user_id = client.session_meta().unwrap().user_id.to_owned();
1119
1120 let mut room = http::response::Room::new();
1122 let event = Raw::new(&json!({
1123 "type": "m.room.member",
1124 "sender": user_id,
1125 "content": {
1126 "is_direct": true,
1127 "membership": "join",
1128 },
1129 "state_key": user_id,
1130 }))
1131 .expect("Failed to make raw event")
1132 .cast();
1133 room.invite_state = Some(vec![event]);
1134
1135 let response = response_with_room(room_id, room);
1136 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1137
1138 let client_room = client.get_room(room_id).expect("No room found");
1140 assert_eq!(client_room.state(), RoomState::Invited);
1141 }
1142
1143 #[async_test]
1144 async fn test_left_a_room_from_required_state_event() {
1145 let client = logged_in_base_client(None).await;
1147 let room_id = room_id!("!r:e.uk");
1148 let user_id = user_id!("@u:e.uk");
1149
1150 let mut room = http::response::Room::new();
1152 set_room_joined(&mut room, user_id);
1153 let response = response_with_room(room_id, room);
1154 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1155 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1156
1157 let mut room = http::response::Room::new();
1159 set_room_left(&mut room, user_id);
1160 let response = response_with_room(room_id, room);
1161 let sync_resp =
1162 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1163
1164 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1166
1167 assert!(!sync_resp.rooms.join.contains_key(room_id));
1169 assert!(sync_resp.rooms.leave.contains_key(room_id));
1170 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1171 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1172 }
1173
1174 #[async_test]
1175 async fn test_kick_or_ban_updates_room_to_left() {
1176 for membership in [MembershipState::Leave, MembershipState::Ban] {
1177 let room_id = room_id!("!r:e.uk");
1178 let user_a_id = user_id!("@a:e.uk");
1179 let user_b_id = user_id!("@b:e.uk");
1180 let client = logged_in_base_client(Some(user_a_id)).await;
1181
1182 let mut room = http::response::Room::new();
1184 set_room_joined(&mut room, user_a_id);
1185 let response = response_with_room(room_id, room);
1186 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1187 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1188
1189 let mut room = http::response::Room::new();
1191 room.required_state.push(make_state_event(
1192 user_b_id,
1193 user_a_id.as_str(),
1194 RoomMemberEventContent::new(membership.clone()),
1195 None,
1196 ));
1197 let response = response_with_room(room_id, room);
1198 let sync_resp =
1199 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1200
1201 match membership {
1202 MembershipState::Leave => {
1203 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1205 }
1206 MembershipState::Ban => {
1207 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1209 }
1210 _ => panic!("Unexpected membership state found: {membership}"),
1211 }
1212
1213 assert!(!sync_resp.rooms.join.contains_key(room_id));
1215 assert!(sync_resp.rooms.leave.contains_key(room_id));
1216 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1217 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1218 }
1219 }
1220
1221 #[async_test]
1222 async fn test_left_a_room_from_timeline_state_event() {
1223 let client = logged_in_base_client(None).await;
1225 let room_id = room_id!("!r:e.uk");
1226 let user_id = user_id!("@u:e.uk");
1227
1228 let mut room = http::response::Room::new();
1230 set_room_joined(&mut room, user_id);
1231 let response = response_with_room(room_id, room);
1232 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1233 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1234
1235 let mut room = http::response::Room::new();
1237 set_room_left_as_timeline_event(&mut room, user_id);
1238 let response = response_with_room(room_id, room);
1239 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1240
1241 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1243 }
1244
1245 #[async_test]
1246 async fn test_can_be_reinvited_to_a_left_room() {
1247 let client = logged_in_base_client(None).await;
1251 let room_id = room_id!("!r:e.uk");
1252 let user_id = user_id!("@u:e.uk");
1253
1254 let mut room = http::response::Room::new();
1256 set_room_joined(&mut room, user_id);
1257 let response = response_with_room(room_id, room);
1258 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1259 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1261
1262 let mut room = http::response::Room::new();
1264 set_room_left(&mut room, user_id);
1265 let response = response_with_room(room_id, room);
1266 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1267 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1269
1270 let mut room = http::response::Room::new();
1272 set_room_invited(&mut room, user_id, user_id);
1273 let response = response_with_room(room_id, room);
1274 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1275
1276 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1278 }
1279
1280 #[async_test]
1281 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1282 let room_id = room_id!("!r:e.uk");
1283 let user_a_id = user_id!("@a:e.uk");
1284 let user_b_id = user_id!("@b:e.uk");
1285
1286 let client = logged_in_base_client(None).await;
1288 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1289
1290 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1292 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1293
1294 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1296
1297 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1301 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1302 }
1303
1304 #[async_test]
1305 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
1306 ) {
1307 let room_id = room_id!("!r:e.uk");
1308 let user_a_id = user_id!("@a:e.uk");
1309 let user_b_id = user_id!("@b:e.uk");
1310
1311 let client = logged_in_base_client(None).await;
1313 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1314
1315 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1317 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1318
1319 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1321
1322 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1326 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1327 }
1328
1329 #[async_test]
1330 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1331 let room_id = room_id!("!r:bar.org");
1332 let user_a_id = user_id!("@a:bar.org");
1333 let user_b_id = user_id!("@b:bar.org");
1334
1335 let client = logged_in_base_client(None).await;
1337 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1338
1339 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1341
1342 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1344 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1345
1346 let room = client.get_room(room_id).unwrap();
1347
1348 assert_eq!(room.active_members_count(), 2);
1349 assert_eq!(room.joined_members_count(), 2);
1350 assert_eq!(room.invited_members_count(), 0);
1351 }
1352
1353 #[async_test]
1354 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1355 let room_id = room_id!("!r:bar.org");
1356 let user_a_id = user_id!("@a:bar.org");
1357 let user_b_id = user_id!("@b:bar.org");
1358
1359 let client = logged_in_base_client(None).await;
1361 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1362
1363 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1365
1366 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1368 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1369
1370 let room = client.get_room(room_id).unwrap();
1371
1372 assert_eq!(room.active_members_count(), 2);
1373 assert_eq!(room.joined_members_count(), 1);
1374 assert_eq!(room.invited_members_count(), 1);
1375 }
1376
1377 #[async_test]
1378 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1379 let client = logged_in_base_client(None).await;
1381 let room_id = room_id!("!r:e.uk");
1382
1383 let room = {
1385 let mut room = http::response::Room::new();
1386 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1387
1388 room
1389 };
1390 let response = response_with_room(room_id, room);
1391 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1392
1393 let client_room = client.get_room(room_id).expect("No room found");
1395 assert_eq!(
1396 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1397 "med1"
1398 );
1399 }
1400
1401 #[async_test]
1402 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1403 let client = logged_in_base_client(None).await;
1405 let room_id = room_id!("!r:e.uk");
1406
1407 let room = {
1411 let mut room = http::response::Room::new();
1412 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1413
1414 room
1415 };
1416 let response = response_with_room(room_id, room);
1417 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1418
1419 let client_room = client.get_room(room_id).expect("No room found");
1421 assert_eq!(
1422 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1423 "med1"
1424 );
1425
1426 let room = http::response::Room::new();
1430 let response = response_with_room(room_id, room);
1431 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1432
1433 let client_room = client.get_room(room_id).expect("No room found");
1435 assert_eq!(
1436 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1437 "med1"
1438 );
1439
1440 let room = {
1444 let mut room = http::response::Room::new();
1445 room.avatar = JsOption::Null;
1446
1447 room
1448 };
1449 let response = response_with_room(room_id, room);
1450 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1451
1452 let client_room = client.get_room(room_id).expect("No room found");
1454 assert!(client_room.avatar_url().is_none());
1455 }
1456
1457 #[async_test]
1458 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1459 let client = logged_in_base_client(None).await;
1461 let room_id = room_id!("!r:e.uk");
1462 let user_id = user_id!("@u:e.uk");
1463
1464 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1466 let response = response_with_room(room_id, room);
1467 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1468
1469 let client_room = client.get_room(room_id).expect("No room found");
1471 assert_eq!(
1472 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1473 "med1"
1474 );
1475 }
1476
1477 #[async_test]
1478 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1479 let client = logged_in_base_client(None).await;
1481 let room_id = room_id!("!r:e.uk");
1482 let user_id = user_id!("@u:e.uk");
1483
1484 let mut room = http::response::Room::new();
1486 set_room_invited(&mut room, user_id, user_id);
1487 let response = response_with_room(room_id, room);
1488 let sync_resp =
1489 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1490
1491 let client_room = client.get_room(room_id).expect("No room found");
1493 assert_eq!(client_room.room_id(), room_id);
1494 assert_eq!(client_room.state(), RoomState::Invited);
1495
1496 assert!(!sync_resp.rooms.invite[room_id].invite_state.is_empty());
1498 assert!(!sync_resp.rooms.join.contains_key(room_id));
1499 }
1500
1501 #[async_test]
1502 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1503 let client = logged_in_base_client(None).await;
1505 let room_id = room_id!("!r:e.uk");
1506 let user_id = user_id!("@u:e.uk");
1507
1508 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1510 set_room_invited(&mut room, user_id, user_id);
1511 let response = response_with_room(room_id, room);
1512 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1513
1514 let client_room = client.get_room(room_id).expect("No room found");
1516 assert_eq!(
1517 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1518 "med1"
1519 );
1520 }
1521
1522 #[async_test]
1523 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
1524 ) {
1525 let client = logged_in_base_client(None).await;
1527 let room_id = room_id!("!r:e.uk");
1528 let user_id = user_id!("@u:e.uk");
1529 let room_alias_id = room_alias_id!("#myroom:e.uk");
1530
1531 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1533 set_room_invited(&mut room, user_id, user_id);
1534 let response = response_with_room(room_id, room);
1535 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1536
1537 let client_room = client.get_room(room_id).expect("No room found");
1539 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1540 }
1541
1542 #[async_test]
1543 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1544 let client = logged_in_base_client(None).await;
1546 let room_id = room_id!("!r:e.uk");
1547 let user_id = user_id!("@u:e.uk");
1548 let room_alias_id = room_alias_id!("#myroom:e.uk");
1549
1550 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1553 room.name = Some("This came from the server".to_owned());
1554 let response = response_with_room(room_id, room);
1555 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1556
1557 let client_room = client.get_room(room_id).expect("No room found");
1559 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "myroom");
1560 assert!(client_room.name().is_none());
1561 }
1562
1563 #[async_test]
1564 async fn test_compute_heroes_from_sliding_sync() {
1565 let client = logged_in_base_client(None).await;
1567 let room_id = room_id!("!r:e.uk");
1568 let gordon = user_id!("@gordon:e.uk").to_owned();
1569 let alice = user_id!("@alice:e.uk").to_owned();
1570
1571 let mut room = http::response::Room::new();
1574 room.heroes = Some(vec![
1575 assign!(http::response::Hero::new(gordon), {
1576 name: Some("Gordon".to_owned()),
1577 }),
1578 assign!(http::response::Hero::new(alice), {
1579 name: Some("Alice".to_owned()),
1580 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1581 }),
1582 ]);
1583 let response = response_with_room(room_id, room);
1584 let _sync_resp =
1585 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1586
1587 let client_room = client.get_room(room_id).expect("No room found");
1589 assert_eq!(client_room.room_id(), room_id);
1590 assert_eq!(client_room.state(), RoomState::Joined);
1591
1592 assert_eq!(
1594 client_room.clone_info().summary.heroes(),
1595 &[
1596 RoomHero {
1597 user_id: owned_user_id!("@gordon:e.uk"),
1598 display_name: Some("Gordon".to_owned()),
1599 avatar_url: None
1600 },
1601 RoomHero {
1602 user_id: owned_user_id!("@alice:e.uk"),
1603 display_name: Some("Alice".to_owned()),
1604 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1605 },
1606 ]
1607 );
1608 }
1609
1610 #[async_test]
1611 async fn test_last_event_from_sliding_sync_is_cached() {
1612 let client = logged_in_base_client(None).await;
1614 let room_id = room_id!("!r:e.uk");
1615 let event_a = json!({
1616 "sender":"@alice:example.com",
1617 "type":"m.room.message",
1618 "event_id": "$ida",
1619 "origin_server_ts": 12344446,
1620 "content":{"body":"A", "msgtype": "m.text"}
1621 });
1622 let event_b = json!({
1623 "sender":"@alice:example.com",
1624 "type":"m.room.message",
1625 "event_id": "$idb",
1626 "origin_server_ts": 12344447,
1627 "content":{"body":"B", "msgtype": "m.text"}
1628 });
1629
1630 let events = &[event_a, event_b.clone()];
1632 let room = room_with_timeline(events);
1633 let response = response_with_room(room_id, room);
1634 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1635
1636 let client_room = client.get_room(room_id).expect("No room found");
1638 assert_eq!(
1639 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1640 "$idb"
1641 );
1642 }
1643
1644 #[async_test]
1645 async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1646 let own_user_id = user_id!("@me:e.uk");
1647 let client = logged_in_base_client(Some(own_user_id)).await;
1649 let room_id = room_id!("!r:e.uk");
1650
1651 let power_levels = json!({
1653 "sender":"@alice:example.com",
1654 "state_key":"",
1655 "type":"m.room.power_levels",
1656 "event_id": "$idb",
1657 "origin_server_ts": 12344445,
1658 "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1659 "room_id": room_id,
1660 });
1661
1662 let knock_event = json!({
1664 "sender":"@alice:example.com",
1665 "state_key":"@alice:example.com",
1666 "type":"m.room.member",
1667 "event_id": "$ida",
1668 "origin_server_ts": 12344446,
1669 "content":{"membership": "knock"},
1670 "room_id": room_id,
1671 });
1672
1673 let events = &[knock_event];
1675 let mut room = room_with_timeline(events);
1676 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1677 let response = response_with_room(room_id, room);
1678 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1679
1680 let client_room = client.get_room(room_id).expect("No room found");
1682 assert_eq!(
1683 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1684 "$ida"
1685 );
1686 }
1687
1688 #[async_test]
1689 async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1690 let own_user_id = user_id!("@me:e.uk");
1691 let client = logged_in_base_client(Some(own_user_id)).await;
1693 let room_id = room_id!("!r:e.uk");
1694
1695 let power_levels = json!({
1698 "sender":"@alice:example.com",
1699 "state_key":"",
1700 "type":"m.room.power_levels",
1701 "event_id": "$idb",
1702 "origin_server_ts": 12344445,
1703 "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1704 "room_id": room_id,
1705 });
1706
1707 let knock_event = json!({
1709 "sender":"@alice:example.com",
1710 "state_key":"@alice:example.com",
1711 "type":"m.room.member",
1712 "event_id": "$ida",
1713 "origin_server_ts": 12344446,
1714 "content":{"membership": "knock"},
1715 "room_id": room_id,
1716 });
1717
1718 let events = &[knock_event];
1720 let mut room = room_with_timeline(events);
1721 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1722 let response = response_with_room(room_id, room);
1723 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1724
1725 let client_room = client.get_room(room_id).expect("No room found");
1727 assert!(client_room.latest_event().is_none());
1728 }
1729
1730 #[async_test]
1731 async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1732 let client = logged_in_base_client(None).await;
1734 let room_id = room_id!("!r:e.uk");
1735 let join_event = json!({
1737 "sender":"@alice:example.com",
1738 "state_key":"@alice:example.com",
1739 "type":"m.room.member",
1740 "event_id": "$ida",
1741 "origin_server_ts": 12344446,
1742 "content":{"membership": "join"},
1743 "room_id": room_id,
1744 });
1745
1746 let events = &[join_event];
1748 let room = room_with_timeline(events);
1749 let response = response_with_room(room_id, room);
1750 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1751
1752 let client_room = client.get_room(room_id).expect("No room found");
1754 assert!(client_room.latest_event().is_none());
1755 }
1756
1757 #[async_test]
1758 async fn test_cached_latest_event_can_be_redacted() {
1759 let client = logged_in_base_client(None).await;
1761 let room_id = room_id!("!r:e.uk");
1762 let event_a = json!({
1763 "sender": "@alice:example.com",
1764 "type": "m.room.message",
1765 "event_id": "$ida",
1766 "origin_server_ts": 12344446,
1767 "content": { "body":"A", "msgtype": "m.text" },
1768 });
1769
1770 let room = room_with_timeline(&[event_a]);
1772 let response = response_with_room(room_id, room);
1773 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1774
1775 let client_room = client.get_room(room_id).expect("No room found");
1777 assert_eq!(
1778 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1779 "$ida"
1780 );
1781
1782 let redaction = json!({
1783 "sender": "@alice:example.com",
1784 "type": "m.room.redaction",
1785 "event_id": "$idb",
1786 "redacts": "$ida",
1787 "origin_server_ts": 12344448,
1788 "content": {},
1789 });
1790
1791 let room = room_with_timeline(&[redaction]);
1793 let response = response_with_room(room_id, room);
1794 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1795
1796 let client_room = client.get_room(room_id).expect("No room found");
1798 let latest_event = client_room.latest_event().unwrap();
1799 assert_eq!(latest_event.event_id().unwrap(), "$ida");
1800
1801 assert_matches!(
1803 latest_event.event().raw().deserialize().unwrap(),
1804 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1805 SyncRoomMessageEvent::Redacted(_)
1806 ))
1807 );
1808 }
1809
1810 #[cfg(feature = "e2e-encryption")]
1811 #[async_test]
1812 async fn test_when_no_events_we_dont_cache_any() {
1813 let events = &[];
1814 let chosen = choose_event_to_cache(events).await;
1815 assert!(chosen.is_none());
1816 }
1817
1818 #[cfg(feature = "e2e-encryption")]
1819 #[async_test]
1820 async fn test_when_only_one_event_we_cache_it() {
1821 let event1 = make_event("m.room.message", "$1");
1822 let events = &[event1.clone()];
1823 let chosen = choose_event_to_cache(events).await;
1824 assert_eq!(ev_id(chosen), rawev_id(event1));
1825 }
1826
1827 #[cfg(feature = "e2e-encryption")]
1828 #[async_test]
1829 async fn test_with_multiple_events_we_cache_the_last_one() {
1830 let event1 = make_event("m.room.message", "$1");
1831 let event2 = make_event("m.room.message", "$2");
1832 let events = &[event1, event2.clone()];
1833 let chosen = choose_event_to_cache(events).await;
1834 assert_eq!(ev_id(chosen), rawev_id(event2));
1835 }
1836
1837 #[cfg(feature = "e2e-encryption")]
1838 #[async_test]
1839 async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1840 let event1 = make_event("m.room.message", "$1");
1841 let event2 = make_event("m.room.message", "$2");
1842 let event3 = make_event("m.room.powerlevels", "$3");
1843 let event4 = make_event("m.room.powerlevels", "$5");
1844 let events = &[event1, event2.clone(), event3, event4];
1845 let chosen = choose_event_to_cache(events).await;
1846 assert_eq!(ev_id(chosen), rawev_id(event2));
1847 }
1848
1849 #[cfg(feature = "e2e-encryption")]
1850 #[async_test]
1851 async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1852 let event1 = make_event("m.room.power_levels", "$1");
1853 let events = &[event1];
1854 let chosen = choose_event_to_cache(events).await;
1855 assert!(chosen.is_none());
1856 }
1857
1858 #[cfg(feature = "e2e-encryption")]
1859 #[async_test]
1860 async fn test_cache_encrypted_events_that_are_after_latest_message() {
1861 let event1 = make_event("m.room.message", "$1");
1863 let event2 = make_event("m.room.message", "$2");
1864 let event3 = make_encrypted_event("$3");
1865 let event4 = make_encrypted_event("$4");
1866 let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
1867
1868 let room = make_room();
1870 let mut room_info = room.clone_info();
1871 cache_latest_events(&room, &mut room_info, events, None, None).await;
1872
1873 assert_eq!(
1875 ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
1876 rawev_id(event2.clone())
1877 );
1878
1879 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1880 assert_eq!(
1881 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1882 rawev_id(event2)
1883 );
1884
1885 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
1887 }
1888
1889 #[cfg(feature = "e2e-encryption")]
1890 #[async_test]
1891 async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
1892 let event1 = make_encrypted_event("$1");
1894 let event2 = make_event("m.room.message", "$2");
1895 let event3 = make_encrypted_event("$3");
1896 let events = &[event1, event2.clone(), event3.clone()];
1897
1898 let room = make_room();
1900 let mut room_info = room.clone_info();
1901 cache_latest_events(&room, &mut room_info, events, None, None).await;
1902 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1903
1904 assert_eq!(
1906 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1907 rawev_id(event2)
1908 );
1909
1910 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
1912 }
1913
1914 #[cfg(feature = "e2e-encryption")]
1915 #[async_test]
1916 async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
1917 let event1 = make_event("m.room.message", "$1");
1920 let event2 = make_event("m.room.message", "$2");
1921 let event3 = make_encrypted_event("$3");
1922 let event4 = make_event("m.read", "$4");
1923 let event5 = make_encrypted_event("$5");
1924 let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
1925
1926 let room = make_room();
1928 let mut room_info = room.clone_info();
1929 cache_latest_events(&room, &mut room_info, events, None, None).await;
1930 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1931
1932 assert_eq!(
1934 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1935 rawev_id(event2)
1936 );
1937
1938 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
1940 }
1941
1942 #[cfg(feature = "e2e-encryption")]
1943 #[async_test]
1944 async fn test_only_store_the_max_number_of_encrypted_events() {
1945 let evente = make_event("m.room.message", "$e");
1948 let eventd = make_event("m.room.message", "$d");
1949 let eventc = make_encrypted_event("$c");
1950 let event9 = make_encrypted_event("$9");
1951 let event8 = make_encrypted_event("$8");
1952 let event7 = make_encrypted_event("$7");
1953 let eventb = make_event("m.read", "$b");
1954 let event6 = make_encrypted_event("$6");
1955 let event5 = make_encrypted_event("$5");
1956 let event4 = make_encrypted_event("$4");
1957 let event3 = make_encrypted_event("$3");
1958 let event2 = make_encrypted_event("$2");
1959 let eventa = make_event("m.read", "$a");
1960 let event1 = make_encrypted_event("$1");
1961 let event0 = make_encrypted_event("$0");
1962 let events = &[
1963 evente,
1964 eventd.clone(),
1965 eventc,
1966 event9.clone(),
1967 event8.clone(),
1968 event7.clone(),
1969 eventb,
1970 event6.clone(),
1971 event5.clone(),
1972 event4.clone(),
1973 event3.clone(),
1974 event2.clone(),
1975 eventa,
1976 event1.clone(),
1977 event0.clone(),
1978 ];
1979
1980 let room = make_room();
1982 let mut room_info = room.clone_info();
1983 cache_latest_events(&room, &mut room_info, events, None, None).await;
1984 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1985
1986 assert_eq!(
1988 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1989 rawev_id(eventd)
1990 );
1991
1992 assert_eq!(
1994 rawevs_ids(&room.latest_encrypted_events),
1995 evs_ids(&[
1996 event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
1997 ])
1998 );
1999 }
2000
2001 #[cfg(feature = "e2e-encryption")]
2002 #[async_test]
2003 async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
2004 let room = make_room();
2006 let mut room_info = room.clone_info();
2007 cache_latest_events(
2008 &room,
2009 &mut room_info,
2010 &[
2011 make_encrypted_event("$0"),
2012 make_encrypted_event("$1"),
2013 make_encrypted_event("$2"),
2014 make_encrypted_event("$3"),
2015 make_encrypted_event("$4"),
2016 make_encrypted_event("$5"),
2017 make_encrypted_event("$6"),
2018 make_encrypted_event("$7"),
2019 make_encrypted_event("$8"),
2020 make_encrypted_event("$9"),
2021 ],
2022 None,
2023 None,
2024 )
2025 .await;
2026 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2027
2028 assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
2030
2031 let eventa = make_encrypted_event("$a");
2033 let mut room_info = room.clone_info();
2034 cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
2035 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2036
2037 assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
2039
2040 assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
2042 }
2043
2044 #[cfg(feature = "e2e-encryption")]
2045 #[async_test]
2046 async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
2047 let room = make_room();
2049 let mut room_info = room.clone_info();
2050 cache_latest_events(
2051 &room,
2052 &mut room_info,
2053 &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
2054 None,
2055 None,
2056 )
2057 .await;
2058 room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
2059
2060 let eventa = make_event("m.room.message", "$a");
2062 let eventb = make_encrypted_event("$b");
2063 cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
2064 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2065
2066 assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
2068
2069 assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
2071 }
2072
2073 #[async_test]
2074 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2075 let client = logged_in_base_client(None).await;
2077 let room_id = room_id!("!r:e.uk");
2078
2079 let room = assign!(http::response::Room::new(), {
2081 bump_stamp: Some(42u32.into()),
2082 });
2083 let response = response_with_room(room_id, room);
2084 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2085
2086 let client_room = client.get_room(room_id).expect("No room found");
2088 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2089 }
2090
2091 #[async_test]
2092 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2093 let client = logged_in_base_client(None).await;
2095 let room_id = room_id!("!r:e.uk");
2096
2097 {
2098 let room = assign!(http::response::Room::new(), {
2100 bump_stamp: Some(42u32.into()),
2101 });
2102 let response = response_with_room(room_id, room);
2103 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2104
2105 let client_room = client.get_room(room_id).expect("No room found");
2107 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2108 }
2109
2110 {
2111 let room = assign!(http::response::Room::new(), {
2113 bump_stamp: None,
2114 });
2115 let response = response_with_room(room_id, room);
2116 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2117
2118 let client_room = client.get_room(room_id).expect("No room found");
2120 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2121 }
2122
2123 {
2124 let room = assign!(http::response::Room::new(), {
2127 bump_stamp: Some(153u32.into()),
2128 });
2129 let response = response_with_room(room_id, room);
2130 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2131
2132 let client_room = client.get_room(room_id).expect("No room found");
2134 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
2135 }
2136 }
2137
2138 #[async_test]
2139 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2140 let client = logged_in_base_client(None).await;
2142 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2143 let room_id = room_id!("!r:e.uk");
2144
2145 let room = assign!(http::response::Room::new(), {
2147 bump_stamp: Some(42u32.into()),
2148 });
2149 let response = response_with_room(room_id, room);
2150 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2151
2152 assert_matches!(
2155 room_info_notable_update_stream.recv().await,
2156 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2157 assert_eq!(received_room_id, room_id);
2158 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2159 }
2160 );
2161
2162 let room = assign!(http::response::Room::new(), {
2164 bump_stamp: Some(43u32.into()),
2165 });
2166 let response = response_with_room(room_id, room);
2167 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2168
2169 assert_matches!(
2171 room_info_notable_update_stream.recv().await,
2172 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2173 assert_eq!(received_room_id, room_id);
2174 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2175 }
2176 );
2177 }
2178
2179 #[async_test]
2180 async fn test_read_receipt_can_trigger_a_notable_update_reason() {
2181 let client = logged_in_base_client(None).await;
2183 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2184
2185 let room_id = room_id!("!r:e.uk");
2187 let room = http::response::Room::new();
2188 let response = response_with_room(room_id, room);
2189 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2190
2191 assert_matches!(
2193 room_info_notable_update_stream.recv().await,
2194 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2195 assert_eq!(received_room_id, room_id);
2196 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2197 }
2198 );
2199
2200 let room_id = room_id!("!r:e.uk");
2203 let events = vec![
2204 make_raw_event("m.room.message", "$3"),
2205 make_raw_event("m.room.message", "$4"),
2206 make_raw_event("m.read", "$5"),
2207 ];
2208 let room = assign!(http::response::Room::new(), {
2209 timeline: events,
2210 });
2211 let response = response_with_room(room_id, room);
2212 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2213
2214 assert_matches!(
2216 room_info_notable_update_stream.recv().await,
2217 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2218 assert_eq!(received_room_id, room_id);
2219 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2220 }
2221 );
2222 }
2223
2224 #[async_test]
2225 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2226 let client = logged_in_base_client(None).await;
2228 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2229
2230 let room_id = room_id!("!r:e.uk");
2232 let room = http::response::Room::new();
2233 let response = response_with_room(room_id, room);
2234 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2235
2236 let _ = room_info_notable_update_stream.recv().await;
2238
2239 let room_id = room_id!("!r:e.uk");
2241 let events = vec![Raw::from_json_string(
2242 json!({
2243 "type": "m.room.member",
2244 "event_id": "$3",
2245 "content": { "membership": "join" },
2246 "sender": "@u:h.uk",
2247 "origin_server_ts": 12344445,
2248 "state_key": "@u:e.uk",
2249 })
2250 .to_string(),
2251 )
2252 .unwrap()];
2253 let room = assign!(http::response::Room::new(), {
2254 required_state: events,
2255 });
2256 let response = response_with_room(room_id, room);
2257 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2258
2259 assert_matches!(
2261 room_info_notable_update_stream.recv().await,
2262 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2263 assert_eq!(received_room_id, room_id);
2264 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2265 }
2266 );
2267
2268 let events = vec![Raw::from_json_string(
2269 json!({
2270 "type": "m.room.member",
2271 "event_id": "$3",
2272 "content": { "membership": "leave" },
2273 "sender": "@u:h.uk",
2274 "origin_server_ts": 12344445,
2275 "state_key": "@u:e.uk",
2276 })
2277 .to_string(),
2278 )
2279 .unwrap()];
2280 let room = assign!(http::response::Room::new(), {
2281 required_state: events,
2282 });
2283 let response = response_with_room(room_id, room);
2284 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2285
2286 let update = room_info_notable_update_stream.recv().await;
2288 assert_matches!(
2289 update,
2290 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2291 assert_eq!(received_room_id, room_id);
2292 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2293 }
2294 );
2295 }
2296
2297 #[async_test]
2298 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2299 let client = logged_in_base_client(None).await;
2301 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2302
2303 let room_id = room_id!("!r:e.uk");
2305 let room = http::response::Room::new();
2306 let response = response_with_room(room_id, room);
2307 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2308
2309 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::UNREAD_MARKER));
2315 }
2316 );
2317
2318 let room_id = room_id!("!r:e.uk");
2321 let room_account_data_events = vec![Raw::from_json_string(
2322 json!({
2323 "type": "com.famedly.marked_unread",
2324 "event_id": "$1",
2325 "content": { "unread": true },
2326 "sender": client.session_meta().unwrap().user_id,
2327 "origin_server_ts": 12344445,
2328 })
2329 .to_string(),
2330 )
2331 .unwrap()];
2332 let mut response = response_with_room(room_id, http::response::Room::new());
2333 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2334
2335 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2336
2337 assert_matches!(
2339 room_info_notable_update_stream.recv().await,
2340 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2341 assert_eq!(received_room_id, room_id);
2342 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2343 }
2344 );
2345
2346 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2348
2349 assert_matches!(
2350 room_info_notable_update_stream.recv().await,
2351 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2352 assert_eq!(received_room_id, room_id);
2353 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2354 }
2355 );
2356
2357 let room_account_data_events = vec![Raw::from_json_string(
2359 json!({
2360 "type": "com.famedly.marked_unread",
2361 "event_id": "$1",
2362 "content": { "unread": false },
2363 "sender": client.session_meta().unwrap().user_id,
2364 "origin_server_ts": 12344445,
2365 })
2366 .to_string(),
2367 )
2368 .unwrap()];
2369 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2370 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2371
2372 assert_matches!(
2373 room_info_notable_update_stream.recv().await,
2374 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2375 assert_eq!(received_room_id, room_id);
2376 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2377 }
2378 );
2379 }
2380
2381 #[async_test]
2382 async fn test_pinned_events_are_updated_on_sync() {
2383 let user_a_id = user_id!("@a:e.uk");
2384 let client = logged_in_base_client(Some(user_a_id)).await;
2385 let room_id = room_id!("!r:e.uk");
2386 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2387
2388 let mut room_response = http::response::Room::new();
2390 set_room_joined(&mut room_response, user_a_id);
2391 let response = response_with_room(room_id, room_response);
2392 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2393
2394 let room = client.get_room(room_id).unwrap();
2396 let pinned_event_ids = room.pinned_event_ids();
2397 assert_matches!(pinned_event_ids, None);
2398
2399 let mut room_response = http::response::Room::new();
2401 room_response.required_state.push(make_state_event(
2402 user_a_id,
2403 "",
2404 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2405 None,
2406 ));
2407 let response = response_with_room(room_id, room_response);
2408 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2409
2410 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2411 assert_eq!(pinned_event_ids.len(), 1);
2412 assert_eq!(pinned_event_ids[0], pinned_event_id);
2413
2414 let mut room_response = http::response::Room::new();
2416 room_response.required_state.push(make_state_event(
2417 user_a_id,
2418 "",
2419 RoomPinnedEventsEventContent::new(Vec::new()),
2420 None,
2421 ));
2422 let response = response_with_room(room_id, room_response);
2423 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2424 let pinned_event_ids = room.pinned_event_ids().unwrap();
2425 assert!(pinned_event_ids.is_empty());
2426 }
2427
2428 #[async_test]
2429 async fn test_dms_are_processed_in_any_sync_response() {
2430 let current_user_id = user_id!("@current:e.uk");
2431 let client = logged_in_base_client(Some(current_user_id)).await;
2432 let user_a_id = user_id!("@a:e.uk");
2433 let user_b_id = user_id!("@b:e.uk");
2434 let room_id_1 = room_id!("!r:e.uk");
2435 let room_id_2 = room_id!("!s:e.uk");
2436
2437 let mut room_response = http::response::Room::new();
2438 set_room_joined(&mut room_response, user_a_id);
2439 let mut response = response_with_room(room_id_1, room_response);
2440 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2441 BTreeMap::new();
2442 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2443 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2444 response
2445 .extensions
2446 .account_data
2447 .global
2448 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2449 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2450
2451 let room_1 = client.get_room(room_id_1).unwrap();
2452 assert!(room_1.is_direct().await.unwrap());
2453
2454 let mut room_response = http::response::Room::new();
2456 set_room_joined(&mut room_response, user_b_id);
2457 let response = response_with_room(room_id_2, room_response);
2458 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2459
2460 let room_2 = client.get_room(room_id_2).unwrap();
2461 assert!(room_2.is_direct().await.unwrap());
2462 }
2463
2464 #[cfg(feature = "e2e-encryption")]
2465 async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2466 let room = make_room();
2467 let mut room_info = room.clone_info();
2468 cache_latest_events(&room, &mut room_info, events, None, None).await;
2469 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2470 room.latest_event().map(|latest_event| latest_event.event().clone())
2471 }
2472
2473 #[cfg(feature = "e2e-encryption")]
2474 fn rawev_id(event: TimelineEvent) -> String {
2475 event.event_id().unwrap().to_string()
2476 }
2477
2478 fn ev_id(event: Option<TimelineEvent>) -> String {
2479 event.unwrap().event_id().unwrap().to_string()
2480 }
2481
2482 #[cfg(feature = "e2e-encryption")]
2483 fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2484 events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2485 }
2486
2487 #[cfg(feature = "e2e-encryption")]
2488 fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2489 events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2490 }
2491
2492 #[cfg(feature = "e2e-encryption")]
2493 fn make_room() -> Room {
2494 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2495
2496 Room::new(
2497 user_id!("@u:e.co"),
2498 Arc::new(MemoryStore::new()),
2499 room_id!("!r:e.co"),
2500 RoomState::Joined,
2501 sender,
2502 )
2503 }
2504
2505 fn make_raw_event(typ: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2506 Raw::from_json_string(
2507 json!({
2508 "type": typ,
2509 "event_id": id,
2510 "content": { "msgtype": "m.text", "body": "my msg" },
2511 "sender": "@u:h.uk",
2512 "origin_server_ts": 12344445,
2513 })
2514 .to_string(),
2515 )
2516 .unwrap()
2517 }
2518
2519 #[cfg(feature = "e2e-encryption")]
2520 fn make_event(typ: &str, id: &str) -> TimelineEvent {
2521 TimelineEvent::new(make_raw_event(typ, id))
2522 }
2523
2524 #[cfg(feature = "e2e-encryption")]
2525 fn make_encrypted_event(id: &str) -> TimelineEvent {
2526 TimelineEvent::new_utd_event(
2527 Raw::from_json_string(
2528 json!({
2529 "type": "m.room.encrypted",
2530 "event_id": id,
2531 "content": {
2532 "algorithm": "m.megolm.v1.aes-sha2",
2533 "ciphertext": "",
2534 "sender_key": "",
2535 "device_id": "",
2536 "session_id": "",
2537 },
2538 "sender": "@u:h.uk",
2539 "origin_server_ts": 12344445,
2540 })
2541 .to_string(),
2542 )
2543 .unwrap(),
2544 UnableToDecryptInfo {
2545 session_id: Some("".to_owned()),
2546 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2547 },
2548 )
2549 }
2550
2551 async fn membership(
2552 client: &BaseClient,
2553 room_id: &RoomId,
2554 user_id: &UserId,
2555 ) -> MembershipState {
2556 let room = client.get_room(room_id).expect("Room not found!");
2557 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2558 member.membership().clone()
2559 }
2560
2561 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2562 let room = client.get_room(room_id).expect("Room not found!");
2563 room.direct_targets()
2564 }
2565
2566 async fn create_dm(
2569 client: &BaseClient,
2570 room_id: &RoomId,
2571 my_id: &UserId,
2572 their_id: &UserId,
2573 other_state: MembershipState,
2574 ) {
2575 let mut room = http::response::Room::new();
2576 set_room_joined(&mut room, my_id);
2577
2578 match other_state {
2579 MembershipState::Join => {
2580 room.joined_count = Some(uint!(2));
2581 room.invited_count = None;
2582 }
2583
2584 MembershipState::Invite => {
2585 room.joined_count = Some(uint!(1));
2586 room.invited_count = Some(uint!(1));
2587 }
2588
2589 _ => {
2590 room.joined_count = Some(uint!(1));
2591 room.invited_count = None;
2592 }
2593 }
2594
2595 room.required_state.push(make_membership_event(their_id, other_state));
2596
2597 let mut response = response_with_room(room_id, room);
2598 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2599 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2600 }
2601
2602 async fn update_room_membership(
2604 client: &BaseClient,
2605 room_id: &RoomId,
2606 user_id: &UserId,
2607 new_state: MembershipState,
2608 ) {
2609 let mut room = http::response::Room::new();
2610 room.required_state.push(make_membership_event(user_id, new_state));
2611 let response = response_with_room(room_id, room);
2612 client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2613 }
2614
2615 fn set_direct_with(
2616 response: &mut http::Response,
2617 user_id: OwnedUserId,
2618 room_ids: Vec<OwnedRoomId>,
2619 ) {
2620 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2621 BTreeMap::new();
2622 direct_content.insert(user_id.into(), room_ids);
2623 response
2624 .extensions
2625 .account_data
2626 .global
2627 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2628 }
2629
2630 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2631 let mut response = http::Response::new("5".to_owned());
2632 response.rooms.insert(room_id.to_owned(), room);
2633 response
2634 }
2635
2636 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2637 let mut room = http::response::Room::new();
2638
2639 let mut avatar_event_content = RoomAvatarEventContent::new();
2640 avatar_event_content.url = Some(avatar_uri.to_owned());
2641
2642 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2643
2644 room
2645 }
2646
2647 fn room_with_canonical_alias(
2648 room_alias_id: &RoomAliasId,
2649 user_id: &UserId,
2650 ) -> http::response::Room {
2651 let mut room = http::response::Room::new();
2652
2653 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2654 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2655
2656 room.required_state.push(make_state_event(
2657 user_id,
2658 "",
2659 canonical_alias_event_content,
2660 None,
2661 ));
2662
2663 room
2664 }
2665
2666 fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2667 let mut room = http::response::Room::new();
2668 room.timeline.extend(
2669 events
2670 .iter()
2671 .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2672 .collect::<Vec<_>>(),
2673 );
2674 room
2675 }
2676
2677 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2678 room.required_state.push(make_state_event(
2679 sender,
2680 "",
2681 RoomNameEventContent::new(name),
2682 None,
2683 ));
2684 }
2685
2686 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2687 let evt = Raw::new(&json!({
2691 "type": "m.room.member",
2692 "sender": inviter,
2693 "content": {
2694 "is_direct": true,
2695 "membership": "invite",
2696 },
2697 "state_key": invitee,
2698 }))
2699 .expect("Failed to make raw event")
2700 .cast();
2701
2702 room.invite_state = Some(vec![evt]);
2703
2704 room.required_state.push(make_state_event(
2707 inviter,
2708 invitee.as_str(),
2709 RoomMemberEventContent::new(MembershipState::Invite),
2710 None,
2711 ));
2712 }
2713
2714 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2715 let evt = Raw::new(&json!({
2719 "type": "m.room.member",
2720 "sender": knocker,
2721 "content": {
2722 "is_direct": true,
2723 "membership": "knock",
2724 },
2725 "state_key": knocker,
2726 }))
2727 .expect("Failed to make raw event")
2728 .cast();
2729
2730 room.invite_state = Some(vec![evt]);
2731 }
2732
2733 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2734 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2735 }
2736
2737 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2738 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2739 }
2740
2741 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2742 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2743 }
2744
2745 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2746 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2747 }
2748
2749 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2750 Raw::new(&json!({
2751 "type": content.event_type(),
2752 "content": content,
2753 }))
2754 .expect("Failed to create account data event")
2755 .cast()
2756 }
2757
2758 fn make_state_event<C: StateEventContent, E>(
2759 sender: &UserId,
2760 state_key: &str,
2761 content: C,
2762 prev_content: Option<C>,
2763 ) -> Raw<E> {
2764 let unsigned = if let Some(prev_content) = prev_content {
2765 json!({ "prev_content": prev_content })
2766 } else {
2767 json!({})
2768 };
2769
2770 Raw::new(&json!({
2771 "type": content.event_type(),
2772 "state_key": state_key,
2773 "content": content,
2774 "event_id": event_id!("$evt"),
2775 "sender": sender,
2776 "origin_server_ts": 10,
2777 "unsigned": unsigned,
2778 }))
2779 .expect("Failed to create state event")
2780 .cast()
2781 }
2782}