1pub mod http;
18
19#[cfg(feature = "e2e-encryption")]
20use std::ops::Deref;
21use std::{borrow::Cow, collections::BTreeMap};
22
23#[cfg(feature = "e2e-encryption")]
24use matrix_sdk_common::deserialized_responses::TimelineEvent;
25use ruma::{
26 api::client::sync::sync_events::v3::{self, InvitedRoom, KnockedRoom},
27 events::{
28 room::member::MembershipState, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
29 AnySyncStateEvent,
30 },
31 serde::Raw,
32 JsOption, OwnedRoomId, RoomId, UInt, UserId,
33};
34#[cfg(feature = "e2e-encryption")]
35use ruma::{api::client::sync::sync_events::v5, events::AnyToDeviceEvent, events::StateEventType};
36use tracing::{debug, error, instrument, trace, warn};
37
38use super::BaseClient;
39use crate::{
40 error::Result,
41 read_receipts::{compute_unread_counts, PreviousEventsProvider},
42 response_processors::AccountDataProcessor,
43 rooms::{
44 normal::{RoomHero, RoomInfoNotableUpdateReasons},
45 RoomState,
46 },
47 ruma::assign,
48 store::{ambiguity_map::AmbiguityCache, StateChanges, Store},
49 sync::{JoinedRoomUpdate, LeftRoomUpdate, Notification, RoomUpdates, SyncResponse},
50 Room, RoomInfo,
51};
52#[cfg(feature = "e2e-encryption")]
53use crate::{
54 latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
55 RoomMemberships,
56};
57
58impl BaseClient {
59 #[cfg(feature = "e2e-encryption")]
60 pub async fn process_sliding_sync_e2ee(
68 &self,
69 to_device: Option<&v5::response::ToDevice>,
70 e2ee: &v5::response::E2EE,
71 ) -> Result<Option<Vec<Raw<AnyToDeviceEvent>>>> {
72 if to_device.is_none() && e2ee.is_empty() {
73 return Ok(None);
74 }
75
76 let to_device_events =
77 to_device.as_ref().map(|to_device| to_device.events.clone()).unwrap_or_default();
78
79 trace!(
80 to_device_events = to_device_events.len(),
81 device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
82 device_unused_fallback_key_types =
83 e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
84 "Processing sliding sync e2ee events",
85 );
86
87 let mut changes = StateChanges::default();
88 let mut room_info_notable_updates =
89 BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
90
91 let to_device = self
95 .preprocess_to_device_events(
96 matrix_sdk_crypto::EncryptionSyncChanges {
97 to_device_events,
98 changed_devices: &e2ee.device_lists,
99 one_time_keys_counts: &e2ee.device_one_time_keys_count,
100 unused_fallback_keys: e2ee.device_unused_fallback_key_types.as_deref(),
101 next_batch_token: to_device
102 .as_ref()
103 .map(|to_device| to_device.next_batch.clone()),
104 },
105 &mut changes,
106 &mut room_info_notable_updates,
107 )
108 .await?;
109
110 trace!("ready to submit e2ee changes to store");
111 self.store.save_changes(&changes).await?;
112 self.apply_changes(&changes, room_info_notable_updates);
113 trace!("applied e2ee changes");
114
115 Ok(Some(to_device))
116 }
117
118 #[instrument(skip_all, level = "trace")]
129 pub async fn process_sliding_sync<PEP: PreviousEventsProvider>(
130 &self,
131 response: &http::Response,
132 previous_events_provider: &PEP,
133 with_msc4186: bool,
134 ) -> Result<SyncResponse> {
135 let http::Response {
136 rooms,
140 lists,
141 extensions,
142 ..
145 } = response;
146
147 trace!(
148 rooms = rooms.len(),
149 lists = lists.len(),
150 has_extensions = !extensions.is_empty(),
151 "Processing sliding sync room events"
152 );
153
154 if rooms.is_empty() && extensions.is_empty() {
155 return Ok(SyncResponse::default());
158 };
159
160 let mut changes = StateChanges::default();
161 let mut room_info_notable_updates =
162 BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
163
164 let store = self.store.clone();
165 let mut ambiguity_cache = AmbiguityCache::new(store.inner.clone());
166
167 let account_data_processor = AccountDataProcessor::process(&extensions.account_data.global);
168
169 let mut new_rooms = RoomUpdates::default();
170 let mut notifications = Default::default();
171 let mut rooms_account_data = extensions.account_data.rooms.clone();
172
173 let user_id = self
174 .session_meta()
175 .expect("Sliding sync shouldn't run without an authenticated user.")
176 .user_id
177 .to_owned();
178
179 for (room_id, response_room_data) in rooms {
180 let (room_info, joined_room, left_room, invited_room, knocked_room) = self
181 .process_sliding_sync_room(
182 room_id,
183 response_room_data,
184 &mut rooms_account_data,
185 &store,
186 &user_id,
187 &account_data_processor,
188 &mut changes,
189 &mut room_info_notable_updates,
190 &mut notifications,
191 &mut ambiguity_cache,
192 with_msc4186,
193 )
194 .await?;
195
196 changes.add_room(room_info);
197
198 if let Some(joined_room) = joined_room {
199 new_rooms.join.insert(room_id.clone(), joined_room);
200 }
201
202 if let Some(left_room) = left_room {
203 new_rooms.leave.insert(room_id.clone(), left_room);
204 }
205
206 if let Some(invited_room) = invited_room {
207 new_rooms.invite.insert(room_id.clone(), invited_room);
208 }
209
210 if let Some(knocked_room) = knocked_room {
211 new_rooms.knocked.insert(room_id.clone(), knocked_room);
212 }
213 }
214
215 for (room_id, raw) in &extensions.receipts.rooms {
220 match raw.deserialize() {
221 Ok(event) => {
222 changes.add_receipts(room_id, event.content);
223 }
224 Err(e) => {
225 let event_id: Option<String> = raw.get_field("event_id").ok().flatten();
226 #[rustfmt::skip]
227 warn!(
228 ?room_id, event_id,
229 "Failed to deserialize read receipt room event: {e}"
230 );
231 }
232 }
233
234 new_rooms
236 .join
237 .entry(room_id.to_owned())
238 .or_insert_with(JoinedRoomUpdate::default)
239 .ephemeral
240 .push(raw.clone().cast());
241 }
242
243 for (room_id, raw) in &extensions.typing.rooms {
244 new_rooms
246 .join
247 .entry(room_id.to_owned())
248 .or_insert_with(JoinedRoomUpdate::default)
249 .ephemeral
250 .push(raw.clone().cast());
251 }
252
253 for (room_id, raw) in &rooms_account_data {
255 self.handle_room_account_data(
256 room_id,
257 raw,
258 &mut changes,
259 &mut room_info_notable_updates,
260 )
261 .await;
262
263 if let Some(room) = self.store.room(room_id) {
264 match room.state() {
265 RoomState::Joined => new_rooms
266 .join
267 .entry(room_id.to_owned())
268 .or_insert_with(JoinedRoomUpdate::default)
269 .account_data
270 .append(&mut raw.to_vec()),
271 RoomState::Left | RoomState::Banned => new_rooms
272 .leave
273 .entry(room_id.to_owned())
274 .or_insert_with(LeftRoomUpdate::default)
275 .account_data
276 .append(&mut raw.to_vec()),
277 RoomState::Invited | RoomState::Knocked => {}
278 }
279 }
280 }
281
282 let user_id = &self.session_meta().expect("logged in user").user_id;
285
286 for (room_id, joined_room_update) in &mut new_rooms.join {
287 if let Some(mut room_info) = changes
288 .room_infos
289 .get(room_id)
290 .cloned()
291 .or_else(|| self.get_room(room_id).map(|r| r.clone_info()))
292 {
293 let prev_read_receipts = room_info.read_receipts.clone();
294
295 compute_unread_counts(
296 user_id,
297 room_id,
298 changes.receipts.get(room_id),
299 previous_events_provider.for_room(room_id),
300 &joined_room_update.timeline.events,
301 &mut room_info.read_receipts,
302 );
303
304 if prev_read_receipts != room_info.read_receipts {
305 room_info_notable_updates
306 .entry(room_id.clone())
307 .or_default()
308 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
309
310 changes.add_room(room_info);
311 }
312 }
313 }
314
315 account_data_processor.apply(&mut changes, &store).await;
316
317 changes.ambiguity_maps = ambiguity_cache.cache;
328
329 trace!("ready to submit changes to store");
330 store.save_changes(&changes).await?;
331 self.apply_changes(&changes, room_info_notable_updates);
332 trace!("applied changes");
333
334 new_rooms.update_in_memory_caches(&self.store).await;
340
341 Ok(SyncResponse {
342 rooms: new_rooms,
343 notifications,
344 presence: Default::default(),
346 account_data: extensions.account_data.global.clone(),
347 to_device: Default::default(),
348 })
349 }
350
351 #[allow(clippy::too_many_arguments)]
352 async fn process_sliding_sync_room(
353 &self,
354 room_id: &RoomId,
355 room_data: &http::response::Room,
356 rooms_account_data: &mut BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
357 store: &Store,
358 user_id: &UserId,
359 account_data_processor: &AccountDataProcessor,
360 changes: &mut StateChanges,
361 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
362 notifications: &mut BTreeMap<OwnedRoomId, Vec<Notification>>,
363 ambiguity_cache: &mut AmbiguityCache,
364 with_msc4186: bool,
365 ) -> Result<(
366 RoomInfo,
367 Option<JoinedRoomUpdate>,
368 Option<LeftRoomUpdate>,
369 Option<InvitedRoom>,
370 Option<KnockedRoom>,
371 )> {
372 let mut room_data = Cow::Borrowed(room_data);
376
377 let (raw_state_events, state_events): (Vec<_>, Vec<_>) = {
378 let state_events = Self::deserialize_state_events(&room_data.required_state);
380
381 state_events.into_iter().unzip()
386 };
387
388 let is_new_room = !store.room_exists(room_id);
390
391 if !with_msc4186 && room_data.bump_stamp.is_none() {
409 if let Some(invite_state) = &room_data.invite_state {
410 room_data.to_mut().bump_stamp =
411 invite_state.iter().rev().find_map(|invite_state| {
412 invite_state.get_field::<UInt>("origin_server_ts").ok().flatten()
413 });
414
415 debug!(
416 ?room_id,
417 timestamp = ?room_data.bump_stamp,
418 "Received a room with no `timestamp`; looked for a replacement value"
419 );
420 } else {
421 error!(?room_id, "Received a room with no `timestamp` and no `invite_state`");
422 }
423 }
424
425 let stripped_state: Option<Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>> =
426 room_data
427 .invite_state
428 .as_ref()
429 .map(|invite_state| Self::deserialize_stripped_state_events(invite_state));
430
431 #[allow(unused_mut)] let (mut room, mut room_info, invited_room, knocked_room) = self
433 .process_sliding_sync_room_membership(
434 &state_events,
435 stripped_state.as_ref(),
436 store,
437 user_id,
438 room_id,
439 room_info_notable_updates,
440 );
441
442 room_info.mark_state_partially_synced();
443
444 let mut user_ids = if !state_events.is_empty() {
445 self.handle_state(
446 &raw_state_events,
447 &state_events,
448 &mut room_info,
449 changes,
450 ambiguity_cache,
451 )
452 .await?
453 } else {
454 Default::default()
455 };
456
457 let push_rules = self.get_push_rules(account_data_processor).await?;
458
459 if let Some(invite_state) = &stripped_state {
461 self.handle_invited_state(
462 &room,
463 invite_state,
464 &push_rules,
465 &mut room_info,
466 changes,
467 notifications,
468 )
469 .await?;
470 }
471
472 process_room_properties(
473 room_id,
474 room_data.as_ref(),
475 &mut room_info,
476 is_new_room,
477 room_info_notable_updates,
478 );
479
480 let timeline = self
481 .handle_timeline(
482 &room,
483 room_data.limited,
484 room_data.timeline.clone(),
485 true,
486 room_data.prev_batch.clone(),
487 &push_rules,
488 &mut user_ids,
489 &mut room_info,
490 changes,
491 notifications,
492 ambiguity_cache,
493 )
494 .await?;
495
496 #[cfg(feature = "e2e-encryption")]
499 cache_latest_events(&room, &mut room_info, &timeline.events, Some(changes), Some(store))
500 .await;
501
502 #[cfg(feature = "e2e-encryption")]
503 if room_info.is_encrypted() {
504 if let Some(o) = self.olm_machine().await.as_ref() {
505 if !room.is_encrypted() {
506 let user_ids = store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
510 o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
511 }
512
513 if !user_ids.is_empty() {
514 o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?;
515 }
516 }
517 }
518
519 let notification_count = room_data.unread_notifications.clone().into();
520 room_info.update_notification_count(notification_count);
521
522 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
523 let room_account_data = rooms_account_data.get(room_id).cloned();
524
525 match room_info.state() {
526 RoomState::Joined => {
527 let ephemeral = Vec::new();
531
532 Ok((
533 room_info,
534 Some(JoinedRoomUpdate::new(
535 timeline,
536 raw_state_events,
537 room_account_data.unwrap_or_default(),
538 ephemeral,
539 notification_count,
540 ambiguity_changes,
541 )),
542 None,
543 None,
544 None,
545 ))
546 }
547
548 RoomState::Left | RoomState::Banned => Ok((
549 room_info,
550 None,
551 Some(LeftRoomUpdate::new(
552 timeline,
553 raw_state_events,
554 room_account_data.unwrap_or_default(),
555 ambiguity_changes,
556 )),
557 None,
558 None,
559 )),
560
561 RoomState::Invited => Ok((room_info, None, None, invited_room, None)),
562
563 RoomState::Knocked => Ok((room_info, None, None, None, knocked_room)),
564 }
565 }
566
567 fn process_sliding_sync_room_membership(
573 &self,
574 state_events: &[AnySyncStateEvent],
575 stripped_state: Option<&Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>>,
576 store: &Store,
577 user_id: &UserId,
578 room_id: &RoomId,
579 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
580 ) -> (Room, RoomInfo, Option<InvitedRoom>, Option<KnockedRoom>) {
581 if let Some(stripped_state) = stripped_state {
582 let room = store.get_or_create_room(
583 room_id,
584 RoomState::Invited,
585 self.room_info_notable_update_sender.clone(),
586 );
587 let mut room_info = room.clone_info();
588
589 let membership_event_content = stripped_state.iter().find_map(|(_, event)| {
592 if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
593 if membership_event.state_key == user_id {
594 return Some(membership_event.content.clone());
595 }
596 }
597 None
598 });
599
600 if let Some(membership_event_content) = membership_event_content {
601 if membership_event_content.membership == MembershipState::Knock {
602 room_info.mark_as_knocked();
604 let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect();
605 let knock_state = assign!(v3::KnockState::default(), { events: raw_events });
606 let knocked_room =
607 assign!(KnockedRoom::default(), { knock_state: knock_state });
608 return (room, room_info, None, Some(knocked_room));
609 }
610 }
611
612 room_info.mark_as_invited();
614 let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect::<Vec<_>>();
615 let invited_room = InvitedRoom::from(v3::InviteState::from(raw_events));
616 (room, room_info, Some(invited_room), None)
617 } else {
618 let room = store.get_or_create_room(
619 room_id,
620 RoomState::Joined,
621 self.room_info_notable_update_sender.clone(),
622 );
623 let mut room_info = room.clone_info();
624
625 room_info.mark_as_joined();
630
631 self.handle_own_room_membership(
637 state_events,
638 &mut room_info,
639 room_info_notable_updates,
640 );
641
642 (room, room_info, None, None)
643 }
644 }
645
646 pub(crate) fn handle_own_room_membership(
649 &self,
650 state_events: &[AnySyncStateEvent],
651 room_info: &mut RoomInfo,
652 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
653 ) {
654 let Some(meta) = self.session_meta() else {
655 return;
656 };
657
658 for event in state_events.iter().rev() {
662 if let AnySyncStateEvent::RoomMember(member) = &event {
663 if member.state_key() == meta.user_id.as_str() {
666 let new_state: RoomState = member.membership().into();
667 if new_state != room_info.state() {
668 room_info.set_state(new_state);
669 room_info_notable_updates
671 .entry(room_info.room_id.to_owned())
672 .or_default()
673 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
674 }
675 break;
676 }
677 }
678 }
679 }
680}
681
682#[cfg(feature = "e2e-encryption")]
690async fn cache_latest_events(
691 room: &Room,
692 room_info: &mut RoomInfo,
693 events: &[TimelineEvent],
694 changes: Option<&StateChanges>,
695 store: Option<&Store>,
696) {
697 use crate::{
698 deserialized_responses::DisplayName, store::ambiguity_map::is_display_name_ambiguous,
699 };
700
701 let mut encrypted_events =
702 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
703
704 let power_levels_from_changes = || {
706 let state_changes = changes?.state.get(room_info.room_id())?;
707 let room_power_levels_state =
708 state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
709 match room_power_levels_state.deserialize().ok()? {
710 AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
711 _ => None,
712 }
713 };
714
715 let power_levels = match power_levels_from_changes() {
717 Some(power_levels) => Some(power_levels),
718 None => room.power_levels().await.ok(),
719 };
720
721 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
722
723 for event in events.iter().rev() {
724 if let Ok(timeline_event) = event.raw().deserialize() {
725 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
726 PossibleLatestEvent::YesRoomMessage(_)
727 | PossibleLatestEvent::YesPoll(_)
728 | PossibleLatestEvent::YesCallInvite(_)
729 | PossibleLatestEvent::YesCallNotify(_)
730 | PossibleLatestEvent::YesSticker(_)
731 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
732 let mut sender_profile = None;
740 let mut sender_name_is_ambiguous = None;
741
742 if let Some(changes) = changes {
745 sender_profile = changes
746 .profiles
747 .get(room.room_id())
748 .and_then(|profiles_by_user| {
749 profiles_by_user.get(timeline_event.sender())
750 })
751 .cloned();
752
753 if let Some(sender_profile) = sender_profile.as_ref() {
754 sender_name_is_ambiguous = sender_profile
755 .as_original()
756 .and_then(|profile| profile.content.displayname.as_ref())
757 .and_then(|display_name| {
758 let display_name = DisplayName::new(display_name);
759
760 changes.ambiguity_maps.get(room.room_id()).and_then(
761 |map_for_room| {
762 map_for_room.get(&display_name).map(|users| {
763 is_display_name_ambiguous(&display_name, users)
764 })
765 },
766 )
767 });
768 }
769 }
770
771 if sender_profile.is_none() {
773 if let Some(store) = store {
774 sender_profile = store
775 .get_profile(room.room_id(), timeline_event.sender())
776 .await
777 .ok()
778 .flatten();
779
780 }
783 }
784
785 let latest_event = Box::new(LatestEvent::new_with_sender_details(
786 event.clone(),
787 sender_profile,
788 sender_name_is_ambiguous,
789 ));
790
791 room_info.latest_event = Some(latest_event);
793 room.latest_encrypted_events.write().unwrap().clear();
796 break;
799 }
800 PossibleLatestEvent::NoEncrypted => {
801 if encrypted_events.len() < encrypted_events.capacity() {
807 encrypted_events.push(event.raw().clone());
808 }
809 }
810 _ => {
811 }
813 }
814 } else {
815 warn!(
816 "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
817 event.event_id().expect("Event has no ID!")
818 );
819 }
820 }
821
822 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
825}
826
827fn process_room_properties(
828 room_id: &RoomId,
829 room_data: &http::response::Room,
830 room_info: &mut RoomInfo,
831 is_new_room: bool,
832 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
833) {
834 match &room_data.avatar {
840 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
842 JsOption::Null => room_info.update_avatar(None),
844 JsOption::Undefined => {}
846 }
847
848 if let Some(count) = room_data.joined_count {
852 room_info.update_joined_member_count(count.into());
853 }
854 if let Some(count) = room_data.invited_count {
855 room_info.update_invited_member_count(count.into());
856 }
857
858 if let Some(heroes) = &room_data.heroes {
859 room_info.update_heroes(
860 heroes
861 .iter()
862 .map(|hero| RoomHero {
863 user_id: hero.user_id.clone(),
864 display_name: hero.name.clone(),
865 avatar_url: hero.avatar.clone(),
866 })
867 .collect(),
868 );
869 }
870
871 room_info.set_prev_batch(room_data.prev_batch.as_deref());
872
873 if room_data.limited {
874 room_info.mark_members_missing();
875 }
876
877 if let Some(recency_stamp) = &room_data.bump_stamp {
878 let recency_stamp: u64 = (*recency_stamp).into();
879
880 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
881 room_info.update_recency_stamp(recency_stamp);
882
883 if !is_new_room {
887 room_info_notable_updates
888 .entry(room_id.to_owned())
889 .or_default()
890 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
891 }
892 }
893 }
894}
895
896#[cfg(all(test, not(target_family = "wasm")))]
897mod tests {
898 use std::collections::{BTreeMap, HashSet};
899 #[cfg(feature = "e2e-encryption")]
900 use std::sync::{Arc, RwLock as SyncRwLock};
901
902 use assert_matches::assert_matches;
903 use matrix_sdk_common::deserialized_responses::TimelineEvent;
904 #[cfg(feature = "e2e-encryption")]
905 use matrix_sdk_common::{
906 deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
907 ring_buffer::RingBuffer,
908 };
909 use matrix_sdk_test::async_test;
910 use ruma::{
911 api::client::sync::sync_events::UnreadNotificationsCount,
912 assign, event_id,
913 events::{
914 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
915 room::{
916 avatar::RoomAvatarEventContent,
917 canonical_alias::RoomCanonicalAliasEventContent,
918 member::{MembershipState, RoomMemberEventContent},
919 message::SyncRoomMessageEvent,
920 name::RoomNameEventContent,
921 pinned_events::RoomPinnedEventsEventContent,
922 },
923 AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
924 StateEventContent,
925 },
926 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
927 serde::Raw,
928 uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
929 };
930 use serde_json::json;
931
932 #[cfg(feature = "e2e-encryption")]
933 use super::cache_latest_events;
934 use super::http;
935 use crate::{
936 rooms::normal::{RoomHero, RoomInfoNotableUpdateReasons},
937 test_utils::logged_in_base_client,
938 BaseClient, RoomInfoNotableUpdate, RoomState,
939 };
940 #[cfg(feature = "e2e-encryption")]
941 use crate::{store::MemoryStore, Room};
942
943 #[async_test]
944 async fn test_notification_count_set() {
945 let client = logged_in_base_client(None).await;
946
947 let mut response = http::Response::new("42".to_owned());
948 let room_id = room_id!("!room:example.org");
949 let count = assign!(UnreadNotificationsCount::default(), {
950 highlight_count: Some(uint!(13)),
951 notification_count: Some(uint!(37)),
952 });
953
954 response.rooms.insert(
955 room_id.to_owned(),
956 assign!(http::response::Room::new(), {
957 unread_notifications: count.clone()
958 }),
959 );
960
961 let sync_response = client
962 .process_sliding_sync(&response, &(), true)
963 .await
964 .expect("Failed to process sync");
965
966 let room = sync_response.rooms.join.get(room_id).unwrap();
968 assert_eq!(room.unread_notifications, count.clone().into());
969
970 let room = client.get_room(room_id).expect("found room");
972 assert_eq!(room.unread_notification_counts(), count.into());
973 }
974
975 #[async_test]
976 async fn test_can_process_empty_sliding_sync_response() {
977 let client = logged_in_base_client(None).await;
978 let empty_response = http::Response::new("5".to_owned());
979 client
980 .process_sliding_sync(&empty_response, &(), true)
981 .await
982 .expect("Failed to process sync");
983 }
984
985 #[async_test]
986 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
987 let client = logged_in_base_client(None).await;
989 let room_id = room_id!("!r:e.uk");
990
991 let mut room = http::response::Room::new();
994 room.joined_count = Some(uint!(41));
995 let response = response_with_room(room_id, room);
996 let sync_resp = client
997 .process_sliding_sync(&response, &(), true)
998 .await
999 .expect("Failed to process sync");
1000
1001 let client_room = client.get_room(room_id).expect("No room found");
1003 assert_eq!(client_room.room_id(), room_id);
1004 assert_eq!(client_room.joined_members_count(), 41);
1005 assert_eq!(client_room.state(), RoomState::Joined);
1006
1007 assert!(sync_resp.rooms.join.contains_key(room_id));
1009 assert!(!sync_resp.rooms.leave.contains_key(room_id));
1010 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1011 }
1012
1013 #[async_test]
1014 async fn test_missing_room_name_event() {
1015 let client = logged_in_base_client(None).await;
1017 let room_id = room_id!("!r:e.uk");
1018
1019 let mut room = http::response::Room::new();
1022 room.name = Some("little room".to_owned());
1023 let response = response_with_room(room_id, room);
1024 let sync_resp = client
1025 .process_sliding_sync(&response, &(), true)
1026 .await
1027 .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 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "Empty Room");
1033 assert_eq!(client_room.state(), RoomState::Joined);
1034
1035 assert!(sync_resp.rooms.join.contains_key(room_id));
1037 assert!(!sync_resp.rooms.leave.contains_key(room_id));
1038 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1039 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1040 }
1041
1042 #[async_test]
1043 async fn test_room_name_event() {
1044 let client = logged_in_base_client(None).await;
1046 let room_id = room_id!("!r:e.uk");
1047
1048 let mut room = http::response::Room::new();
1051
1052 room.name = Some("little room".to_owned());
1053 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1054
1055 let response = response_with_room(room_id, room);
1056 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1057
1058 let client_room = client.get_room(room_id).expect("No room found");
1060 assert_eq!(client_room.name().as_deref(), Some("The Name"));
1061 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1062 }
1063
1064 #[async_test]
1065 async fn test_missing_invited_room_name_event() {
1066 let client = logged_in_base_client(None).await;
1068 let room_id = room_id!("!r:e.uk");
1069 let user_id = user_id!("@w:e.uk");
1070 let inviter = user_id!("@john:mastodon.org");
1071
1072 let mut room = http::response::Room::new();
1075 set_room_invited(&mut room, inviter, user_id);
1076 room.name = Some("name from sliding sync response".to_owned());
1077 let response = response_with_room(room_id, room);
1078 let sync_resp = client
1079 .process_sliding_sync(&response, &(), true)
1080 .await
1081 .expect("Failed to process sync");
1082
1083 let client_room = client.get_room(room_id).expect("No room found");
1085 assert!(client_room.name().is_none());
1086
1087 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "w");
1089
1090 assert_eq!(client_room.state(), RoomState::Invited);
1091
1092 assert!(!sync_resp.rooms.join.contains_key(room_id));
1094 assert!(!sync_resp.rooms.leave.contains_key(room_id));
1095 assert!(sync_resp.rooms.invite.contains_key(room_id));
1096 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1097 }
1098
1099 #[async_test]
1100 async fn test_invited_room_name_event() {
1101 let client = logged_in_base_client(None).await;
1103 let room_id = room_id!("!r:e.uk");
1104 let user_id = user_id!("@w:e.uk");
1105 let inviter = user_id!("@john:mastodon.org");
1106
1107 let mut room = http::response::Room::new();
1110
1111 set_room_invited(&mut room, inviter, user_id);
1112
1113 room.name = Some("name from sliding sync response".to_owned());
1114 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1115
1116 let response = response_with_room(room_id, room);
1117 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1118
1119 let client_room = client.get_room(room_id).expect("No room found");
1121 assert_eq!(client_room.name().as_deref(), Some("The Name"));
1122 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1123 }
1124
1125 #[async_test]
1126 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
1127 let client = logged_in_base_client(None).await;
1129 let room_id = room_id!("!r:e.uk");
1130 let user_id = client.session_meta().unwrap().user_id.to_owned();
1131
1132 let mut room = http::response::Room::new();
1135 set_room_knocked(&mut room, &user_id);
1136
1137 let response = response_with_room(room_id, room);
1138 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1139
1140 let client_room = client.get_room(room_id).expect("No room found");
1142 assert_eq!(client_room.state(), RoomState::Knocked);
1143 }
1144
1145 #[async_test]
1146 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
1147 ) {
1148 let client = logged_in_base_client(None).await;
1150 let room_id = room_id!("!r:e.uk");
1151 let user_id = user_id!("@w:e.uk");
1152
1153 let mut room = http::response::Room::new();
1155 set_room_knocked(&mut room, user_id);
1156
1157 let response = response_with_room(room_id, room);
1158 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1159
1160 let client_room = client.get_room(room_id).expect("No room found");
1163 assert_eq!(client_room.state(), RoomState::Invited);
1164 }
1165
1166 #[async_test]
1167 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
1168 ) {
1169 let client = logged_in_base_client(None).await;
1171 let room_id = room_id!("!r:e.uk");
1172 let user_id = client.session_meta().unwrap().user_id.to_owned();
1173
1174 let mut room = http::response::Room::new();
1176 let event = Raw::new(&json!({
1177 "type": "m.room.member",
1178 "sender": user_id,
1179 "content": {
1180 "is_direct": true,
1181 "membership": "join",
1182 },
1183 "state_key": user_id,
1184 }))
1185 .expect("Failed to make raw event")
1186 .cast();
1187 room.invite_state = Some(vec![event]);
1188
1189 let response = response_with_room(room_id, room);
1190 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1191
1192 let client_room = client.get_room(room_id).expect("No room found");
1194 assert_eq!(client_room.state(), RoomState::Invited);
1195 }
1196
1197 #[async_test]
1198 async fn test_left_a_room_from_required_state_event() {
1199 let client = logged_in_base_client(None).await;
1201 let room_id = room_id!("!r:e.uk");
1202 let user_id = user_id!("@u:e.uk");
1203
1204 let mut room = http::response::Room::new();
1206 set_room_joined(&mut room, user_id);
1207 let response = response_with_room(room_id, room);
1208 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1209 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1210
1211 let mut room = http::response::Room::new();
1213 set_room_left(&mut room, user_id);
1214 let response = response_with_room(room_id, room);
1215 let sync_resp = client
1216 .process_sliding_sync(&response, &(), true)
1217 .await
1218 .expect("Failed to process sync");
1219
1220 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1222
1223 assert!(!sync_resp.rooms.join.contains_key(room_id));
1225 assert!(sync_resp.rooms.leave.contains_key(room_id));
1226 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1227 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1228 }
1229
1230 #[async_test]
1231 async fn test_kick_or_ban_updates_room_to_left() {
1232 for membership in [MembershipState::Leave, MembershipState::Ban] {
1233 let room_id = room_id!("!r:e.uk");
1234 let user_a_id = user_id!("@a:e.uk");
1235 let user_b_id = user_id!("@b:e.uk");
1236 let client = logged_in_base_client(Some(user_a_id)).await;
1237
1238 let mut room = http::response::Room::new();
1240 set_room_joined(&mut room, user_a_id);
1241 let response = response_with_room(room_id, room);
1242 client
1243 .process_sliding_sync(&response, &(), true)
1244 .await
1245 .expect("Failed to process sync");
1246 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1247
1248 let mut room = http::response::Room::new();
1250 room.required_state.push(make_state_event(
1251 user_b_id,
1252 user_a_id.as_str(),
1253 RoomMemberEventContent::new(membership.clone()),
1254 None,
1255 ));
1256 let response = response_with_room(room_id, room);
1257 let sync_resp = client
1258 .process_sliding_sync(&response, &(), true)
1259 .await
1260 .expect("Failed to process sync");
1261
1262 match membership {
1263 MembershipState::Leave => {
1264 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1266 }
1267 MembershipState::Ban => {
1268 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1270 }
1271 _ => panic!("Unexpected membership state found: {membership}"),
1272 }
1273
1274 assert!(!sync_resp.rooms.join.contains_key(room_id));
1276 assert!(sync_resp.rooms.leave.contains_key(room_id));
1277 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1278 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1279 }
1280 }
1281
1282 #[async_test]
1283 async fn test_left_a_room_from_timeline_state_event() {
1284 let client = logged_in_base_client(None).await;
1286 let room_id = room_id!("!r:e.uk");
1287 let user_id = user_id!("@u:e.uk");
1288
1289 let mut room = http::response::Room::new();
1291 set_room_joined(&mut room, user_id);
1292 let response = response_with_room(room_id, room);
1293 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1294 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1295
1296 let mut room = http::response::Room::new();
1298 set_room_left_as_timeline_event(&mut room, user_id);
1299 let response = response_with_room(room_id, room);
1300 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1301
1302 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1304 }
1305
1306 #[async_test]
1307 async fn test_can_be_reinvited_to_a_left_room() {
1308 let client = logged_in_base_client(None).await;
1312 let room_id = room_id!("!r:e.uk");
1313 let user_id = user_id!("@u:e.uk");
1314
1315 let mut room = http::response::Room::new();
1317 set_room_joined(&mut room, user_id);
1318 let response = response_with_room(room_id, room);
1319 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1320 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1322
1323 let mut room = http::response::Room::new();
1325 set_room_left(&mut room, user_id);
1326 let response = response_with_room(room_id, room);
1327 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1328 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1330
1331 let mut room = http::response::Room::new();
1333 set_room_invited(&mut room, user_id, user_id);
1334 let response = response_with_room(room_id, room);
1335 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1336
1337 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1339 }
1340
1341 #[async_test]
1342 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1343 let room_id = room_id!("!r:e.uk");
1344 let user_a_id = user_id!("@a:e.uk");
1345 let user_b_id = user_id!("@b:e.uk");
1346
1347 let client = logged_in_base_client(None).await;
1349 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1350
1351 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1353 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1354
1355 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1357
1358 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1362 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1363 }
1364
1365 #[async_test]
1366 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
1367 ) {
1368 let room_id = room_id!("!r:e.uk");
1369 let user_a_id = user_id!("@a:e.uk");
1370 let user_b_id = user_id!("@b:e.uk");
1371
1372 let client = logged_in_base_client(None).await;
1374 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1375
1376 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1378 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1379
1380 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1382
1383 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1387 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1388 }
1389
1390 #[async_test]
1391 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1392 let room_id = room_id!("!r:bar.org");
1393 let user_a_id = user_id!("@a:bar.org");
1394 let user_b_id = user_id!("@b:bar.org");
1395
1396 let client = logged_in_base_client(None).await;
1398 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1399
1400 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1402
1403 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1405 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1406
1407 let room = client.get_room(room_id).unwrap();
1408
1409 assert_eq!(room.active_members_count(), 2);
1410 assert_eq!(room.joined_members_count(), 2);
1411 assert_eq!(room.invited_members_count(), 0);
1412 }
1413
1414 #[async_test]
1415 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1416 let room_id = room_id!("!r:bar.org");
1417 let user_a_id = user_id!("@a:bar.org");
1418 let user_b_id = user_id!("@b:bar.org");
1419
1420 let client = logged_in_base_client(None).await;
1422 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1423
1424 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1426
1427 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1429 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1430
1431 let room = client.get_room(room_id).unwrap();
1432
1433 assert_eq!(room.active_members_count(), 2);
1434 assert_eq!(room.joined_members_count(), 1);
1435 assert_eq!(room.invited_members_count(), 1);
1436 }
1437
1438 #[async_test]
1439 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1440 let client = logged_in_base_client(None).await;
1442 let room_id = room_id!("!r:e.uk");
1443
1444 let room = {
1446 let mut room = http::response::Room::new();
1447 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1448
1449 room
1450 };
1451 let response = response_with_room(room_id, room);
1452 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1453
1454 let client_room = client.get_room(room_id).expect("No room found");
1456 assert_eq!(
1457 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1458 "med1"
1459 );
1460 }
1461
1462 #[async_test]
1463 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1464 let client = logged_in_base_client(None).await;
1466 let room_id = room_id!("!r:e.uk");
1467
1468 let room = {
1472 let mut room = http::response::Room::new();
1473 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1474
1475 room
1476 };
1477 let response = response_with_room(room_id, room);
1478 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1479
1480 let client_room = client.get_room(room_id).expect("No room found");
1482 assert_eq!(
1483 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1484 "med1"
1485 );
1486
1487 let room = http::response::Room::new();
1491 let response = response_with_room(room_id, room);
1492 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1493
1494 let client_room = client.get_room(room_id).expect("No room found");
1496 assert_eq!(
1497 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1498 "med1"
1499 );
1500
1501 let room = {
1505 let mut room = http::response::Room::new();
1506 room.avatar = JsOption::Null;
1507
1508 room
1509 };
1510 let response = response_with_room(room_id, room);
1511 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1512
1513 let client_room = client.get_room(room_id).expect("No room found");
1515 assert!(client_room.avatar_url().is_none());
1516 }
1517
1518 #[async_test]
1519 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1520 let client = logged_in_base_client(None).await;
1522 let room_id = room_id!("!r:e.uk");
1523 let user_id = user_id!("@u:e.uk");
1524
1525 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1527 let response = response_with_room(room_id, room);
1528 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1529
1530 let client_room = client.get_room(room_id).expect("No room found");
1532 assert_eq!(
1533 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1534 "med1"
1535 );
1536 }
1537
1538 #[async_test]
1539 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1540 let client = logged_in_base_client(None).await;
1542 let room_id = room_id!("!r:e.uk");
1543 let user_id = user_id!("@u:e.uk");
1544
1545 let mut room = http::response::Room::new();
1547 set_room_invited(&mut room, user_id, user_id);
1548 let response = response_with_room(room_id, room);
1549 let sync_resp = client
1550 .process_sliding_sync(&response, &(), true)
1551 .await
1552 .expect("Failed to process sync");
1553
1554 let client_room = client.get_room(room_id).expect("No room found");
1556 assert_eq!(client_room.room_id(), room_id);
1557 assert_eq!(client_room.state(), RoomState::Invited);
1558
1559 assert!(!sync_resp.rooms.invite[room_id].invite_state.is_empty());
1561 assert!(!sync_resp.rooms.join.contains_key(room_id));
1562 }
1563
1564 #[async_test]
1565 async fn test_invitation_room_receive_a_default_timestamp_on_not_simplified_sliding_sync() {
1566 const NOT_MSC4186: bool = false;
1567
1568 let client = logged_in_base_client(None).await;
1570 let room_id = room_id!("!r:e.uk");
1571 let user_id = user_id!("@u:e.uk");
1572
1573 let mut room = http::response::Room::new();
1576 set_room_invited(&mut room, user_id, user_id);
1577 let response = response_with_room(room_id, room);
1578 let _sync_resp = client
1579 .process_sliding_sync(&response, &(), NOT_MSC4186)
1580 .await
1581 .expect("Failed to process sync");
1582
1583 let client_room = client.get_room(room_id).expect("No room found");
1585 assert!(client_room.recency_stamp().is_none());
1586
1587 let mut room = http::response::Room::new();
1590 set_room_invited(&mut room, user_id, user_id);
1591
1592 if let Some(invite_state) = room.invite_state.as_mut() {
1593 invite_state.push(
1594 Raw::new(&json!({
1595 "type": "m.room.member",
1596 "sender": user_id,
1597 "content": {
1598 "is_direct": true,
1599 "membership": "invite",
1600 },
1601 "state_key": user_id,
1602 "origin_server_ts": 123456789,
1604 }))
1605 .expect("Failed to make raw event")
1606 .cast(),
1607 );
1608 invite_state.push(
1609 Raw::new(&json!({
1610 "type": "m.room.member",
1611 "sender": user_id,
1612 "content": {
1613 "is_direct": true,
1614 "membership": "invite",
1615 },
1616 "state_key": user_id,
1617 }))
1618 .expect("Failed to make raw event")
1619 .cast(),
1620 );
1621 }
1622
1623 let response = response_with_room(room_id, room);
1624 let _sync_resp = client
1625 .process_sliding_sync(&response, &(), NOT_MSC4186)
1626 .await
1627 .expect("Failed to process sync");
1628
1629 let client_room = client.get_room(room_id).expect("No room found");
1631 assert_eq!(client_room.recency_stamp(), Some(123456789));
1632 }
1633
1634 #[async_test]
1635 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1636 let client = logged_in_base_client(None).await;
1638 let room_id = room_id!("!r:e.uk");
1639 let user_id = user_id!("@u:e.uk");
1640
1641 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1643 set_room_invited(&mut room, user_id, user_id);
1644 let response = response_with_room(room_id, room);
1645 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1646
1647 let client_room = client.get_room(room_id).expect("No room found");
1649 assert_eq!(
1650 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1651 "med1"
1652 );
1653 }
1654
1655 #[async_test]
1656 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
1657 ) {
1658 let client = logged_in_base_client(None).await;
1660 let room_id = room_id!("!r:e.uk");
1661 let user_id = user_id!("@u:e.uk");
1662 let room_alias_id = room_alias_id!("#myroom:e.uk");
1663
1664 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1666 set_room_invited(&mut room, user_id, user_id);
1667 let response = response_with_room(room_id, room);
1668 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1669
1670 let client_room = client.get_room(room_id).expect("No room found");
1672 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1673 }
1674
1675 #[async_test]
1676 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1677 let client = logged_in_base_client(None).await;
1679 let room_id = room_id!("!r:e.uk");
1680 let user_id = user_id!("@u:e.uk");
1681 let room_alias_id = room_alias_id!("#myroom:e.uk");
1682
1683 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1686 room.name = Some("This came from the server".to_owned());
1687 let response = response_with_room(room_id, room);
1688 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1689
1690 let client_room = client.get_room(room_id).expect("No room found");
1692 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "myroom");
1693 assert!(client_room.name().is_none());
1694 }
1695
1696 #[async_test]
1697 async fn test_compute_heroes_from_sliding_sync() {
1698 let client = logged_in_base_client(None).await;
1700 let room_id = room_id!("!r:e.uk");
1701 let gordon = user_id!("@gordon:e.uk").to_owned();
1702 let alice = user_id!("@alice:e.uk").to_owned();
1703
1704 let mut room = http::response::Room::new();
1707 room.heroes = Some(vec![
1708 assign!(http::response::Hero::new(gordon), {
1709 name: Some("Gordon".to_owned()),
1710 }),
1711 assign!(http::response::Hero::new(alice), {
1712 name: Some("Alice".to_owned()),
1713 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1714 }),
1715 ]);
1716 let response = response_with_room(room_id, room);
1717 let _sync_resp = client
1718 .process_sliding_sync(&response, &(), true)
1719 .await
1720 .expect("Failed to process sync");
1721
1722 let client_room = client.get_room(room_id).expect("No room found");
1724 assert_eq!(client_room.room_id(), room_id);
1725 assert_eq!(client_room.state(), RoomState::Joined);
1726
1727 assert_eq!(
1729 client_room.clone_info().summary.heroes(),
1730 &[
1731 RoomHero {
1732 user_id: owned_user_id!("@gordon:e.uk"),
1733 display_name: Some("Gordon".to_owned()),
1734 avatar_url: None
1735 },
1736 RoomHero {
1737 user_id: owned_user_id!("@alice:e.uk"),
1738 display_name: Some("Alice".to_owned()),
1739 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1740 },
1741 ]
1742 );
1743 }
1744
1745 #[async_test]
1746 async fn test_last_event_from_sliding_sync_is_cached() {
1747 let client = logged_in_base_client(None).await;
1749 let room_id = room_id!("!r:e.uk");
1750 let event_a = json!({
1751 "sender":"@alice:example.com",
1752 "type":"m.room.message",
1753 "event_id": "$ida",
1754 "origin_server_ts": 12344446,
1755 "content":{"body":"A", "msgtype": "m.text"}
1756 });
1757 let event_b = json!({
1758 "sender":"@alice:example.com",
1759 "type":"m.room.message",
1760 "event_id": "$idb",
1761 "origin_server_ts": 12344447,
1762 "content":{"body":"B", "msgtype": "m.text"}
1763 });
1764
1765 let events = &[event_a, event_b.clone()];
1767 let room = room_with_timeline(events);
1768 let response = response_with_room(room_id, room);
1769 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1770
1771 let client_room = client.get_room(room_id).expect("No room found");
1773 assert_eq!(
1774 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1775 "$idb"
1776 );
1777 }
1778
1779 #[async_test]
1780 async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1781 let own_user_id = user_id!("@me:e.uk");
1782 let client = logged_in_base_client(Some(own_user_id)).await;
1784 let room_id = room_id!("!r:e.uk");
1785
1786 let power_levels = json!({
1788 "sender":"@alice:example.com",
1789 "state_key":"",
1790 "type":"m.room.power_levels",
1791 "event_id": "$idb",
1792 "origin_server_ts": 12344445,
1793 "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1794 "room_id": room_id,
1795 });
1796
1797 let knock_event = json!({
1799 "sender":"@alice:example.com",
1800 "state_key":"@alice:example.com",
1801 "type":"m.room.member",
1802 "event_id": "$ida",
1803 "origin_server_ts": 12344446,
1804 "content":{"membership": "knock"},
1805 "room_id": room_id,
1806 });
1807
1808 let events = &[knock_event];
1810 let mut room = room_with_timeline(events);
1811 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1812 let response = response_with_room(room_id, room);
1813 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1814
1815 let client_room = client.get_room(room_id).expect("No room found");
1817 assert_eq!(
1818 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1819 "$ida"
1820 );
1821 }
1822
1823 #[async_test]
1824 async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1825 let own_user_id = user_id!("@me:e.uk");
1826 let client = logged_in_base_client(Some(own_user_id)).await;
1828 let room_id = room_id!("!r:e.uk");
1829
1830 let power_levels = json!({
1833 "sender":"@alice:example.com",
1834 "state_key":"",
1835 "type":"m.room.power_levels",
1836 "event_id": "$idb",
1837 "origin_server_ts": 12344445,
1838 "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1839 "room_id": room_id,
1840 });
1841
1842 let knock_event = json!({
1844 "sender":"@alice:example.com",
1845 "state_key":"@alice:example.com",
1846 "type":"m.room.member",
1847 "event_id": "$ida",
1848 "origin_server_ts": 12344446,
1849 "content":{"membership": "knock"},
1850 "room_id": room_id,
1851 });
1852
1853 let events = &[knock_event];
1855 let mut room = room_with_timeline(events);
1856 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1857 let response = response_with_room(room_id, room);
1858 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1859
1860 let client_room = client.get_room(room_id).expect("No room found");
1862 assert!(client_room.latest_event().is_none());
1863 }
1864
1865 #[async_test]
1866 async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1867 let client = logged_in_base_client(None).await;
1869 let room_id = room_id!("!r:e.uk");
1870 let join_event = json!({
1872 "sender":"@alice:example.com",
1873 "state_key":"@alice:example.com",
1874 "type":"m.room.member",
1875 "event_id": "$ida",
1876 "origin_server_ts": 12344446,
1877 "content":{"membership": "join"},
1878 "room_id": room_id,
1879 });
1880
1881 let events = &[join_event];
1883 let room = room_with_timeline(events);
1884 let response = response_with_room(room_id, room);
1885 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1886
1887 let client_room = client.get_room(room_id).expect("No room found");
1889 assert!(client_room.latest_event().is_none());
1890 }
1891
1892 #[async_test]
1893 async fn test_cached_latest_event_can_be_redacted() {
1894 let client = logged_in_base_client(None).await;
1896 let room_id = room_id!("!r:e.uk");
1897 let event_a = json!({
1898 "sender": "@alice:example.com",
1899 "type": "m.room.message",
1900 "event_id": "$ida",
1901 "origin_server_ts": 12344446,
1902 "content": { "body":"A", "msgtype": "m.text" },
1903 });
1904
1905 let room = room_with_timeline(&[event_a]);
1907 let response = response_with_room(room_id, room);
1908 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1909
1910 let client_room = client.get_room(room_id).expect("No room found");
1912 assert_eq!(
1913 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1914 "$ida"
1915 );
1916
1917 let redaction = json!({
1918 "sender": "@alice:example.com",
1919 "type": "m.room.redaction",
1920 "event_id": "$idb",
1921 "redacts": "$ida",
1922 "origin_server_ts": 12344448,
1923 "content": {},
1924 });
1925
1926 let room = room_with_timeline(&[redaction]);
1928 let response = response_with_room(room_id, room);
1929 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
1930
1931 let client_room = client.get_room(room_id).expect("No room found");
1933 let latest_event = client_room.latest_event().unwrap();
1934 assert_eq!(latest_event.event_id().unwrap(), "$ida");
1935
1936 assert_matches!(
1938 latest_event.event().raw().deserialize().unwrap(),
1939 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1940 SyncRoomMessageEvent::Redacted(_)
1941 ))
1942 );
1943 }
1944
1945 #[cfg(feature = "e2e-encryption")]
1946 #[async_test]
1947 async fn test_when_no_events_we_dont_cache_any() {
1948 let events = &[];
1949 let chosen = choose_event_to_cache(events).await;
1950 assert!(chosen.is_none());
1951 }
1952
1953 #[cfg(feature = "e2e-encryption")]
1954 #[async_test]
1955 async fn test_when_only_one_event_we_cache_it() {
1956 let event1 = make_event("m.room.message", "$1");
1957 let events = &[event1.clone()];
1958 let chosen = choose_event_to_cache(events).await;
1959 assert_eq!(ev_id(chosen), rawev_id(event1));
1960 }
1961
1962 #[cfg(feature = "e2e-encryption")]
1963 #[async_test]
1964 async fn test_with_multiple_events_we_cache_the_last_one() {
1965 let event1 = make_event("m.room.message", "$1");
1966 let event2 = make_event("m.room.message", "$2");
1967 let events = &[event1, event2.clone()];
1968 let chosen = choose_event_to_cache(events).await;
1969 assert_eq!(ev_id(chosen), rawev_id(event2));
1970 }
1971
1972 #[cfg(feature = "e2e-encryption")]
1973 #[async_test]
1974 async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1975 let event1 = make_event("m.room.message", "$1");
1976 let event2 = make_event("m.room.message", "$2");
1977 let event3 = make_event("m.room.powerlevels", "$3");
1978 let event4 = make_event("m.room.powerlevels", "$5");
1979 let events = &[event1, event2.clone(), event3, event4];
1980 let chosen = choose_event_to_cache(events).await;
1981 assert_eq!(ev_id(chosen), rawev_id(event2));
1982 }
1983
1984 #[cfg(feature = "e2e-encryption")]
1985 #[async_test]
1986 async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1987 let event1 = make_event("m.room.power_levels", "$1");
1988 let events = &[event1];
1989 let chosen = choose_event_to_cache(events).await;
1990 assert!(chosen.is_none());
1991 }
1992
1993 #[cfg(feature = "e2e-encryption")]
1994 #[async_test]
1995 async fn test_cache_encrypted_events_that_are_after_latest_message() {
1996 let event1 = make_event("m.room.message", "$1");
1998 let event2 = make_event("m.room.message", "$2");
1999 let event3 = make_encrypted_event("$3");
2000 let event4 = make_encrypted_event("$4");
2001 let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
2002
2003 let room = make_room();
2005 let mut room_info = room.clone_info();
2006 cache_latest_events(&room, &mut room_info, events, None, None).await;
2007
2008 assert_eq!(
2010 ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
2011 rawev_id(event2.clone())
2012 );
2013
2014 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2015 assert_eq!(
2016 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2017 rawev_id(event2)
2018 );
2019
2020 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
2022 }
2023
2024 #[cfg(feature = "e2e-encryption")]
2025 #[async_test]
2026 async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
2027 let event1 = make_encrypted_event("$1");
2029 let event2 = make_event("m.room.message", "$2");
2030 let event3 = make_encrypted_event("$3");
2031 let events = &[event1, event2.clone(), event3.clone()];
2032
2033 let room = make_room();
2035 let mut room_info = room.clone_info();
2036 cache_latest_events(&room, &mut room_info, events, None, None).await;
2037 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2038
2039 assert_eq!(
2041 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2042 rawev_id(event2)
2043 );
2044
2045 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
2047 }
2048
2049 #[cfg(feature = "e2e-encryption")]
2050 #[async_test]
2051 async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
2052 let event1 = make_event("m.room.message", "$1");
2055 let event2 = make_event("m.room.message", "$2");
2056 let event3 = make_encrypted_event("$3");
2057 let event4 = make_event("m.read", "$4");
2058 let event5 = make_encrypted_event("$5");
2059 let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
2060
2061 let room = make_room();
2063 let mut room_info = room.clone_info();
2064 cache_latest_events(&room, &mut room_info, events, None, None).await;
2065 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2066
2067 assert_eq!(
2069 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2070 rawev_id(event2)
2071 );
2072
2073 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
2075 }
2076
2077 #[cfg(feature = "e2e-encryption")]
2078 #[async_test]
2079 async fn test_only_store_the_max_number_of_encrypted_events() {
2080 let evente = make_event("m.room.message", "$e");
2083 let eventd = make_event("m.room.message", "$d");
2084 let eventc = make_encrypted_event("$c");
2085 let event9 = make_encrypted_event("$9");
2086 let event8 = make_encrypted_event("$8");
2087 let event7 = make_encrypted_event("$7");
2088 let eventb = make_event("m.read", "$b");
2089 let event6 = make_encrypted_event("$6");
2090 let event5 = make_encrypted_event("$5");
2091 let event4 = make_encrypted_event("$4");
2092 let event3 = make_encrypted_event("$3");
2093 let event2 = make_encrypted_event("$2");
2094 let eventa = make_event("m.read", "$a");
2095 let event1 = make_encrypted_event("$1");
2096 let event0 = make_encrypted_event("$0");
2097 let events = &[
2098 evente,
2099 eventd.clone(),
2100 eventc,
2101 event9.clone(),
2102 event8.clone(),
2103 event7.clone(),
2104 eventb,
2105 event6.clone(),
2106 event5.clone(),
2107 event4.clone(),
2108 event3.clone(),
2109 event2.clone(),
2110 eventa,
2111 event1.clone(),
2112 event0.clone(),
2113 ];
2114
2115 let room = make_room();
2117 let mut room_info = room.clone_info();
2118 cache_latest_events(&room, &mut room_info, events, None, None).await;
2119 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2120
2121 assert_eq!(
2123 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2124 rawev_id(eventd)
2125 );
2126
2127 assert_eq!(
2129 rawevs_ids(&room.latest_encrypted_events),
2130 evs_ids(&[
2131 event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
2132 ])
2133 );
2134 }
2135
2136 #[cfg(feature = "e2e-encryption")]
2137 #[async_test]
2138 async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
2139 let room = make_room();
2141 let mut room_info = room.clone_info();
2142 cache_latest_events(
2143 &room,
2144 &mut room_info,
2145 &[
2146 make_encrypted_event("$0"),
2147 make_encrypted_event("$1"),
2148 make_encrypted_event("$2"),
2149 make_encrypted_event("$3"),
2150 make_encrypted_event("$4"),
2151 make_encrypted_event("$5"),
2152 make_encrypted_event("$6"),
2153 make_encrypted_event("$7"),
2154 make_encrypted_event("$8"),
2155 make_encrypted_event("$9"),
2156 ],
2157 None,
2158 None,
2159 )
2160 .await;
2161 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2162
2163 assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
2165
2166 let eventa = make_encrypted_event("$a");
2168 let mut room_info = room.clone_info();
2169 cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
2170 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2171
2172 assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
2174
2175 assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
2177 }
2178
2179 #[cfg(feature = "e2e-encryption")]
2180 #[async_test]
2181 async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
2182 let room = make_room();
2184 let mut room_info = room.clone_info();
2185 cache_latest_events(
2186 &room,
2187 &mut room_info,
2188 &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
2189 None,
2190 None,
2191 )
2192 .await;
2193 room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
2194
2195 let eventa = make_event("m.room.message", "$a");
2197 let eventb = make_encrypted_event("$b");
2198 cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
2199 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2200
2201 assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
2203
2204 assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
2206 }
2207
2208 #[async_test]
2209 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2210 let client = logged_in_base_client(None).await;
2212 let room_id = room_id!("!r:e.uk");
2213
2214 let room = assign!(http::response::Room::new(), {
2216 bump_stamp: Some(42u32.into()),
2217 });
2218 let response = response_with_room(room_id, room);
2219 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2220
2221 let client_room = client.get_room(room_id).expect("No room found");
2223 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2224 }
2225
2226 #[async_test]
2227 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2228 let client = logged_in_base_client(None).await;
2230 let room_id = room_id!("!r:e.uk");
2231
2232 {
2233 let room = assign!(http::response::Room::new(), {
2235 bump_stamp: Some(42u32.into()),
2236 });
2237 let response = response_with_room(room_id, room);
2238 client
2239 .process_sliding_sync(&response, &(), true)
2240 .await
2241 .expect("Failed to process sync");
2242
2243 let client_room = client.get_room(room_id).expect("No room found");
2245 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2246 }
2247
2248 {
2249 let room = assign!(http::response::Room::new(), {
2251 bump_stamp: None,
2252 });
2253 let response = response_with_room(room_id, room);
2254 client
2255 .process_sliding_sync(&response, &(), true)
2256 .await
2257 .expect("Failed to process sync");
2258
2259 let client_room = client.get_room(room_id).expect("No room found");
2261 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2262 }
2263
2264 {
2265 let room = assign!(http::response::Room::new(), {
2268 bump_stamp: Some(153u32.into()),
2269 });
2270 let response = response_with_room(room_id, room);
2271 client
2272 .process_sliding_sync(&response, &(), true)
2273 .await
2274 .expect("Failed to process sync");
2275
2276 let client_room = client.get_room(room_id).expect("No room found");
2278 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
2279 }
2280 }
2281
2282 #[async_test]
2283 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2284 let client = logged_in_base_client(None).await;
2286 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2287 let room_id = room_id!("!r:e.uk");
2288
2289 let room = assign!(http::response::Room::new(), {
2291 bump_stamp: Some(42u32.into()),
2292 });
2293 let response = response_with_room(room_id, room);
2294 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2295
2296 assert_matches!(
2299 room_info_notable_update_stream.recv().await,
2300 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2301 assert_eq!(received_room_id, room_id);
2302 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2303 }
2304 );
2305
2306 let room = assign!(http::response::Room::new(), {
2308 bump_stamp: Some(43u32.into()),
2309 });
2310 let response = response_with_room(room_id, room);
2311 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2312
2313 assert_matches!(
2315 room_info_notable_update_stream.recv().await,
2316 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2317 assert_eq!(received_room_id, room_id);
2318 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2319 }
2320 );
2321 }
2322
2323 #[async_test]
2324 async fn test_read_receipt_can_trigger_a_notable_update_reason() {
2325 let client = logged_in_base_client(None).await;
2327 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2328
2329 let room_id = room_id!("!r:e.uk");
2331 let room = http::response::Room::new();
2332 let response = response_with_room(room_id, room);
2333 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2334
2335 assert_matches!(
2337 room_info_notable_update_stream.recv().await,
2338 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2339 assert_eq!(received_room_id, room_id);
2340 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2341 }
2342 );
2343
2344 let room_id = room_id!("!r:e.uk");
2347 let events = vec![
2348 make_raw_event("m.room.message", "$3"),
2349 make_raw_event("m.room.message", "$4"),
2350 make_raw_event("m.read", "$5"),
2351 ];
2352 let room = assign!(http::response::Room::new(), {
2353 timeline: events,
2354 });
2355 let response = response_with_room(room_id, room);
2356 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2357
2358 assert_matches!(
2360 room_info_notable_update_stream.recv().await,
2361 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2362 assert_eq!(received_room_id, room_id);
2363 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2364 }
2365 );
2366 }
2367
2368 #[async_test]
2369 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2370 let client = logged_in_base_client(None).await;
2372 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2373
2374 let room_id = room_id!("!r:e.uk");
2376 let room = http::response::Room::new();
2377 let response = response_with_room(room_id, room);
2378 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2379
2380 let _ = room_info_notable_update_stream.recv().await;
2382
2383 let room_id = room_id!("!r:e.uk");
2385 let events = vec![Raw::from_json_string(
2386 json!({
2387 "type": "m.room.member",
2388 "event_id": "$3",
2389 "content": { "membership": "join" },
2390 "sender": "@u:h.uk",
2391 "origin_server_ts": 12344445,
2392 "state_key": "@u:e.uk",
2393 })
2394 .to_string(),
2395 )
2396 .unwrap()];
2397 let room = assign!(http::response::Room::new(), {
2398 required_state: events,
2399 });
2400 let response = response_with_room(room_id, room);
2401 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2402
2403 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::MEMBERSHIP));
2409 }
2410 );
2411
2412 let events = vec![Raw::from_json_string(
2413 json!({
2414 "type": "m.room.member",
2415 "event_id": "$3",
2416 "content": { "membership": "leave" },
2417 "sender": "@u:h.uk",
2418 "origin_server_ts": 12344445,
2419 "state_key": "@u:e.uk",
2420 })
2421 .to_string(),
2422 )
2423 .unwrap()];
2424 let room = assign!(http::response::Room::new(), {
2425 required_state: events,
2426 });
2427 let response = response_with_room(room_id, room);
2428 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2429
2430 let update = room_info_notable_update_stream.recv().await;
2432 assert_matches!(
2433 update,
2434 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2435 assert_eq!(received_room_id, room_id);
2436 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2437 }
2438 );
2439 }
2440
2441 #[async_test]
2442 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2443 let client = logged_in_base_client(None).await;
2445 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2446
2447 let room_id = room_id!("!r:e.uk");
2449 let room = http::response::Room::new();
2450 let response = response_with_room(room_id, room);
2451 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2452
2453 assert_matches!(
2455 room_info_notable_update_stream.recv().await,
2456 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2457 assert_eq!(received_room_id, room_id);
2458 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2459 }
2460 );
2461
2462 let room_id = room_id!("!r:e.uk");
2465 let room_account_data_events = vec![Raw::from_json_string(
2466 json!({
2467 "type": "com.famedly.marked_unread",
2468 "event_id": "$1",
2469 "content": { "unread": true },
2470 "sender": client.session_meta().unwrap().user_id,
2471 "origin_server_ts": 12344445,
2472 })
2473 .to_string(),
2474 )
2475 .unwrap()];
2476 let mut response = response_with_room(room_id, http::response::Room::new());
2477 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2478
2479 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2480
2481 assert_matches!(
2483 room_info_notable_update_stream.recv().await,
2484 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2485 assert_eq!(received_room_id, room_id);
2486 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2487 }
2488 );
2489
2490 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2492
2493 assert_matches!(
2494 room_info_notable_update_stream.recv().await,
2495 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2496 assert_eq!(received_room_id, room_id);
2497 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2498 }
2499 );
2500
2501 let room_account_data_events = vec![Raw::from_json_string(
2503 json!({
2504 "type": "com.famedly.marked_unread",
2505 "event_id": "$1",
2506 "content": { "unread": false },
2507 "sender": client.session_meta().unwrap().user_id,
2508 "origin_server_ts": 12344445,
2509 })
2510 .to_string(),
2511 )
2512 .unwrap()];
2513 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2514 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2515
2516 assert_matches!(
2517 room_info_notable_update_stream.recv().await,
2518 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2519 assert_eq!(received_room_id, room_id);
2520 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2521 }
2522 );
2523 }
2524
2525 #[async_test]
2526 async fn test_pinned_events_are_updated_on_sync() {
2527 let user_a_id = user_id!("@a:e.uk");
2528 let client = logged_in_base_client(Some(user_a_id)).await;
2529 let room_id = room_id!("!r:e.uk");
2530 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2531
2532 let mut room_response = http::response::Room::new();
2534 set_room_joined(&mut room_response, user_a_id);
2535 let response = response_with_room(room_id, room_response);
2536 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2537
2538 let room = client.get_room(room_id).unwrap();
2540 let pinned_event_ids = room.pinned_event_ids();
2541 assert_matches!(pinned_event_ids, None);
2542
2543 let mut room_response = http::response::Room::new();
2545 room_response.required_state.push(make_state_event(
2546 user_a_id,
2547 "",
2548 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2549 None,
2550 ));
2551 let response = response_with_room(room_id, room_response);
2552 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2553
2554 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2555 assert_eq!(pinned_event_ids.len(), 1);
2556 assert_eq!(pinned_event_ids[0], pinned_event_id);
2557
2558 let mut room_response = http::response::Room::new();
2560 room_response.required_state.push(make_state_event(
2561 user_a_id,
2562 "",
2563 RoomPinnedEventsEventContent::new(Vec::new()),
2564 None,
2565 ));
2566 let response = response_with_room(room_id, room_response);
2567 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2568 let pinned_event_ids = room.pinned_event_ids().unwrap();
2569 assert!(pinned_event_ids.is_empty());
2570 }
2571
2572 #[async_test]
2573 async fn test_dms_are_processed_in_any_sync_response() {
2574 let current_user_id = user_id!("@current:e.uk");
2575 let client = logged_in_base_client(Some(current_user_id)).await;
2576 let user_a_id = user_id!("@a:e.uk");
2577 let user_b_id = user_id!("@b:e.uk");
2578 let room_id_1 = room_id!("!r:e.uk");
2579 let room_id_2 = room_id!("!s:e.uk");
2580
2581 let mut room_response = http::response::Room::new();
2582 set_room_joined(&mut room_response, user_a_id);
2583 let mut response = response_with_room(room_id_1, room_response);
2584 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2585 BTreeMap::new();
2586 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2587 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2588 response
2589 .extensions
2590 .account_data
2591 .global
2592 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2593 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2594
2595 let room_1 = client.get_room(room_id_1).unwrap();
2596 assert!(room_1.is_direct().await.unwrap());
2597
2598 let mut room_response = http::response::Room::new();
2600 set_room_joined(&mut room_response, user_b_id);
2601 let response = response_with_room(room_id_2, room_response);
2602 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2603
2604 let room_2 = client.get_room(room_id_2).unwrap();
2605 assert!(room_2.is_direct().await.unwrap());
2606 }
2607
2608 #[cfg(feature = "e2e-encryption")]
2609 async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2610 let room = make_room();
2611 let mut room_info = room.clone_info();
2612 cache_latest_events(&room, &mut room_info, events, None, None).await;
2613 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2614 room.latest_event().map(|latest_event| latest_event.event().clone())
2615 }
2616
2617 #[cfg(feature = "e2e-encryption")]
2618 fn rawev_id(event: TimelineEvent) -> String {
2619 event.event_id().unwrap().to_string()
2620 }
2621
2622 fn ev_id(event: Option<TimelineEvent>) -> String {
2623 event.unwrap().event_id().unwrap().to_string()
2624 }
2625
2626 #[cfg(feature = "e2e-encryption")]
2627 fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2628 events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2629 }
2630
2631 #[cfg(feature = "e2e-encryption")]
2632 fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2633 events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2634 }
2635
2636 #[cfg(feature = "e2e-encryption")]
2637 fn make_room() -> Room {
2638 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2639
2640 Room::new(
2641 user_id!("@u:e.co"),
2642 Arc::new(MemoryStore::new()),
2643 room_id!("!r:e.co"),
2644 RoomState::Joined,
2645 sender,
2646 )
2647 }
2648
2649 fn make_raw_event(typ: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2650 Raw::from_json_string(
2651 json!({
2652 "type": typ,
2653 "event_id": id,
2654 "content": { "msgtype": "m.text", "body": "my msg" },
2655 "sender": "@u:h.uk",
2656 "origin_server_ts": 12344445,
2657 })
2658 .to_string(),
2659 )
2660 .unwrap()
2661 }
2662
2663 #[cfg(feature = "e2e-encryption")]
2664 fn make_event(typ: &str, id: &str) -> TimelineEvent {
2665 TimelineEvent::new(make_raw_event(typ, id))
2666 }
2667
2668 #[cfg(feature = "e2e-encryption")]
2669 fn make_encrypted_event(id: &str) -> TimelineEvent {
2670 TimelineEvent::new_utd_event(
2671 Raw::from_json_string(
2672 json!({
2673 "type": "m.room.encrypted",
2674 "event_id": id,
2675 "content": {
2676 "algorithm": "m.megolm.v1.aes-sha2",
2677 "ciphertext": "",
2678 "sender_key": "",
2679 "device_id": "",
2680 "session_id": "",
2681 },
2682 "sender": "@u:h.uk",
2683 "origin_server_ts": 12344445,
2684 })
2685 .to_string(),
2686 )
2687 .unwrap(),
2688 UnableToDecryptInfo {
2689 session_id: Some("".to_owned()),
2690 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2691 },
2692 )
2693 }
2694
2695 async fn membership(
2696 client: &BaseClient,
2697 room_id: &RoomId,
2698 user_id: &UserId,
2699 ) -> MembershipState {
2700 let room = client.get_room(room_id).expect("Room not found!");
2701 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2702 member.membership().clone()
2703 }
2704
2705 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2706 let room = client.get_room(room_id).expect("Room not found!");
2707 room.direct_targets()
2708 }
2709
2710 async fn create_dm(
2713 client: &BaseClient,
2714 room_id: &RoomId,
2715 my_id: &UserId,
2716 their_id: &UserId,
2717 other_state: MembershipState,
2718 ) {
2719 let mut room = http::response::Room::new();
2720 set_room_joined(&mut room, my_id);
2721
2722 match other_state {
2723 MembershipState::Join => {
2724 room.joined_count = Some(uint!(2));
2725 room.invited_count = None;
2726 }
2727
2728 MembershipState::Invite => {
2729 room.joined_count = Some(uint!(1));
2730 room.invited_count = Some(uint!(1));
2731 }
2732
2733 _ => {
2734 room.joined_count = Some(uint!(1));
2735 room.invited_count = None;
2736 }
2737 }
2738
2739 room.required_state.push(make_membership_event(their_id, other_state));
2740
2741 let mut response = response_with_room(room_id, room);
2742 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2743 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2744 }
2745
2746 async fn update_room_membership(
2748 client: &BaseClient,
2749 room_id: &RoomId,
2750 user_id: &UserId,
2751 new_state: MembershipState,
2752 ) {
2753 let mut room = http::response::Room::new();
2754 room.required_state.push(make_membership_event(user_id, new_state));
2755 let response = response_with_room(room_id, room);
2756 client.process_sliding_sync(&response, &(), true).await.expect("Failed to process sync");
2757 }
2758
2759 fn set_direct_with(
2760 response: &mut http::Response,
2761 user_id: OwnedUserId,
2762 room_ids: Vec<OwnedRoomId>,
2763 ) {
2764 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2765 BTreeMap::new();
2766 direct_content.insert(user_id.into(), room_ids);
2767 response
2768 .extensions
2769 .account_data
2770 .global
2771 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2772 }
2773
2774 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2775 let mut response = http::Response::new("5".to_owned());
2776 response.rooms.insert(room_id.to_owned(), room);
2777 response
2778 }
2779
2780 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2781 let mut room = http::response::Room::new();
2782
2783 let mut avatar_event_content = RoomAvatarEventContent::new();
2784 avatar_event_content.url = Some(avatar_uri.to_owned());
2785
2786 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2787
2788 room
2789 }
2790
2791 fn room_with_canonical_alias(
2792 room_alias_id: &RoomAliasId,
2793 user_id: &UserId,
2794 ) -> http::response::Room {
2795 let mut room = http::response::Room::new();
2796
2797 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2798 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2799
2800 room.required_state.push(make_state_event(
2801 user_id,
2802 "",
2803 canonical_alias_event_content,
2804 None,
2805 ));
2806
2807 room
2808 }
2809
2810 fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2811 let mut room = http::response::Room::new();
2812 room.timeline.extend(
2813 events
2814 .iter()
2815 .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2816 .collect::<Vec<_>>(),
2817 );
2818 room
2819 }
2820
2821 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2822 room.required_state.push(make_state_event(
2823 sender,
2824 "",
2825 RoomNameEventContent::new(name),
2826 None,
2827 ));
2828 }
2829
2830 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2831 let evt = Raw::new(&json!({
2835 "type": "m.room.member",
2836 "sender": inviter,
2837 "content": {
2838 "is_direct": true,
2839 "membership": "invite",
2840 },
2841 "state_key": invitee,
2842 }))
2843 .expect("Failed to make raw event")
2844 .cast();
2845
2846 room.invite_state = Some(vec![evt]);
2847
2848 room.required_state.push(make_state_event(
2851 inviter,
2852 invitee.as_str(),
2853 RoomMemberEventContent::new(MembershipState::Invite),
2854 None,
2855 ));
2856 }
2857
2858 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2859 let evt = Raw::new(&json!({
2863 "type": "m.room.member",
2864 "sender": knocker,
2865 "content": {
2866 "is_direct": true,
2867 "membership": "knock",
2868 },
2869 "state_key": knocker,
2870 }))
2871 .expect("Failed to make raw event")
2872 .cast();
2873
2874 room.invite_state = Some(vec![evt]);
2875 }
2876
2877 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2878 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2879 }
2880
2881 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2882 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2883 }
2884
2885 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2886 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2887 }
2888
2889 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2890 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2891 }
2892
2893 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2894 Raw::new(&json!({
2895 "type": content.event_type(),
2896 "content": content,
2897 }))
2898 .expect("Failed to create account data event")
2899 .cast()
2900 }
2901
2902 fn make_state_event<C: StateEventContent, E>(
2903 sender: &UserId,
2904 state_key: &str,
2905 content: C,
2906 prev_content: Option<C>,
2907 ) -> Raw<E> {
2908 let unsigned = if let Some(prev_content) = prev_content {
2909 json!({ "prev_content": prev_content })
2910 } else {
2911 json!({})
2912 };
2913
2914 Raw::new(&json!({
2915 "type": content.event_type(),
2916 "state_key": state_key,
2917 "content": content,
2918 "event_id": event_id!("$evt"),
2919 "sender": sender,
2920 "origin_server_ts": 10,
2921 "unsigned": unsigned,
2922 }))
2923 .expect("Failed to create state event")
2924 .cast()
2925 }
2926}