1use std::collections::BTreeMap;
18
19#[cfg(feature = "e2e-encryption")]
20use matrix_sdk_common::deserialized_responses::TimelineEvent;
21#[cfg(feature = "e2e-encryption")]
22use ruma::events::AnyToDeviceEvent;
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, StateEventType,
31 },
32 serde::Raw,
33 JsOption, OwnedRoomId, RoomId, UserId,
34};
35use tracing::{instrument, trace, warn};
36
37use super::BaseClient;
38#[cfg(feature = "e2e-encryption")]
39use crate::latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent};
40use crate::{
41 error::Result,
42 read_receipts::{compute_unread_counts, PreviousEventsProvider},
43 response_processors as processors,
44 rooms::{
45 normal::{RoomHero, RoomInfoNotableUpdateReasons},
46 RoomState,
47 },
48 ruma::assign,
49 store::{ambiguity_map::AmbiguityCache, BaseStateStore, StateChanges},
50 sync::{JoinedRoomUpdate, LeftRoomUpdate, Notification, RoomUpdates, SyncResponse},
51 RequestedRequiredStates, Room, RoomInfo,
52};
53
54impl BaseClient {
55 #[cfg(feature = "e2e-encryption")]
56 pub async fn process_sliding_sync_e2ee(
64 &self,
65 to_device: Option<&http::response::ToDevice>,
66 e2ee: &http::response::E2EE,
67 ) -> Result<Option<Vec<Raw<AnyToDeviceEvent>>>> {
68 if to_device.is_none() && e2ee.is_empty() {
69 return Ok(None);
70 }
71
72 trace!(
73 to_device_events =
74 to_device.map(|to_device| to_device.events.len()).unwrap_or_default(),
75 device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
76 device_unused_fallback_key_types =
77 e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
78 "Processing sliding sync e2ee events",
79 );
80
81 let olm_machine = self.olm_machine().await;
82
83 let mut context = processors::Context::new(StateChanges::default(), Default::default());
84
85 let processors::e2ee::to_device::Output { decrypted_to_device_events, room_key_updates } =
86 processors::e2ee::to_device::from_msc4186(
87 &mut context,
88 to_device,
89 e2ee,
90 olm_machine.as_ref(),
91 )
92 .await?;
93
94 processors::latest_event::decrypt_from_rooms(
95 &mut context,
96 room_key_updates
97 .into_iter()
98 .flatten()
99 .filter_map(|room_key_info| self.get_room(&room_key_info.room_id))
100 .collect(),
101 olm_machine.as_ref(),
102 self.decryption_trust_requirement,
103 self.handle_verification_events,
104 )
105 .await?;
106
107 processors::changes::save_and_apply(
108 context,
109 &self.state_store,
110 &self.ignore_user_list_changes,
111 None,
112 )
113 .await?;
114
115 Ok(Some(decrypted_to_device_events))
116 }
117
118 #[instrument(skip_all, level = "trace")]
127 pub async fn process_sliding_sync<PEP: PreviousEventsProvider>(
128 &self,
129 response: &http::Response,
130 previous_events_provider: &PEP,
131 requested_required_states: &RequestedRequiredStates,
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 context = processors::Context::new(StateChanges::default(), Default::default());
159
160 let state_store = self.state_store.clone();
161 let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
162
163 let global_account_data_processor =
164 processors::account_data::global(&extensions.account_data.global);
165
166 let mut new_rooms = RoomUpdates::default();
167 let mut notifications = Default::default();
168 let mut rooms_account_data = extensions.account_data.rooms.clone();
169
170 let user_id = self
171 .session_meta()
172 .expect("Sliding sync shouldn't run without an authenticated user.")
173 .user_id
174 .to_owned();
175
176 for (room_id, response_room_data) in rooms {
177 let (room_info, joined_room, left_room, invited_room, knocked_room) = self
178 .process_sliding_sync_room(
179 &mut context,
180 room_id,
181 requested_required_states.for_room(room_id),
182 response_room_data,
183 &mut rooms_account_data,
184 &state_store,
185 &user_id,
186 &global_account_data_processor,
187 &mut notifications,
188 &mut ambiguity_cache,
189 )
190 .await?;
191
192 context.state_changes.add_room(room_info);
193
194 if let Some(joined_room) = joined_room {
195 new_rooms.join.insert(room_id.clone(), joined_room);
196 }
197
198 if let Some(left_room) = left_room {
199 new_rooms.leave.insert(room_id.clone(), left_room);
200 }
201
202 if let Some(invited_room) = invited_room {
203 new_rooms.invite.insert(room_id.clone(), invited_room);
204 }
205
206 if let Some(knocked_room) = knocked_room {
207 new_rooms.knocked.insert(room_id.clone(), knocked_room);
208 }
209 }
210
211 for (room_id, raw) in &extensions.receipts.rooms {
216 match raw.deserialize() {
217 Ok(event) => {
218 context.state_changes.add_receipts(room_id, event.content);
219 }
220 Err(e) => {
221 let event_id: Option<String> = raw.get_field("event_id").ok().flatten();
222 #[rustfmt::skip]
223 warn!(
224 ?room_id, event_id,
225 "Failed to deserialize read receipt room event: {e}"
226 );
227 }
228 }
229
230 new_rooms
232 .join
233 .entry(room_id.to_owned())
234 .or_insert_with(JoinedRoomUpdate::default)
235 .ephemeral
236 .push(raw.clone().cast());
237 }
238
239 for (room_id, raw) in &extensions.typing.rooms {
240 new_rooms
242 .join
243 .entry(room_id.to_owned())
244 .or_insert_with(JoinedRoomUpdate::default)
245 .ephemeral
246 .push(raw.clone().cast());
247 }
248
249 for (room_id, raw) in &rooms_account_data {
251 processors::account_data::for_room(&mut context, room_id, raw, &self.state_store).await;
252
253 if let Some(room) = self.state_store.room(room_id) {
254 match room.state() {
255 RoomState::Joined => new_rooms
256 .join
257 .entry(room_id.to_owned())
258 .or_insert_with(JoinedRoomUpdate::default)
259 .account_data
260 .append(&mut raw.to_vec()),
261 RoomState::Left | RoomState::Banned => new_rooms
262 .leave
263 .entry(room_id.to_owned())
264 .or_insert_with(LeftRoomUpdate::default)
265 .account_data
266 .append(&mut raw.to_vec()),
267 RoomState::Invited | RoomState::Knocked => {}
268 }
269 }
270 }
271
272 let user_id = &self.session_meta().expect("logged in user").user_id;
275
276 for (room_id, joined_room_update) in &mut new_rooms.join {
277 if let Some(mut room_info) = context
278 .state_changes
279 .room_infos
280 .get(room_id)
281 .cloned()
282 .or_else(|| self.get_room(room_id).map(|r| r.clone_info()))
283 {
284 let prev_read_receipts = room_info.read_receipts.clone();
285
286 compute_unread_counts(
287 user_id,
288 room_id,
289 context.state_changes.receipts.get(room_id),
290 previous_events_provider.for_room(room_id),
291 &joined_room_update.timeline.events,
292 &mut room_info.read_receipts,
293 );
294
295 if prev_read_receipts != room_info.read_receipts {
296 context
297 .room_info_notable_updates
298 .entry(room_id.clone())
299 .or_default()
300 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
301
302 context.state_changes.add_room(room_info);
303 }
304 }
305 }
306
307 global_account_data_processor.apply(&mut context, &state_store).await;
308
309 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
320
321 processors::changes::save_and_apply(
322 context,
323 &self.state_store,
324 &self.ignore_user_list_changes,
325 None,
326 )
327 .await?;
328
329 new_rooms.update_in_memory_caches(&self.state_store).await;
335
336 Ok(SyncResponse {
337 rooms: new_rooms,
338 notifications,
339 presence: Default::default(),
341 account_data: extensions.account_data.global.clone(),
342 to_device: Default::default(),
343 })
344 }
345
346 #[allow(clippy::too_many_arguments)]
347 async fn process_sliding_sync_room(
348 &self,
349 context: &mut processors::Context,
350 room_id: &RoomId,
351 requested_required_states: &[(StateEventType, String)],
352 room_data: &http::response::Room,
353 rooms_account_data: &mut BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
354 state_store: &BaseStateStore,
355 user_id: &UserId,
356 global_account_data_processor: &processors::account_data::Global,
357 notifications: &mut BTreeMap<OwnedRoomId, Vec<Notification>>,
358 ambiguity_cache: &mut AmbiguityCache,
359 ) -> Result<(
360 RoomInfo,
361 Option<JoinedRoomUpdate>,
362 Option<LeftRoomUpdate>,
363 Option<InvitedRoom>,
364 Option<KnockedRoom>,
365 )> {
366 let (raw_state_events, state_events) =
372 processors::state_events::sync::collect(context, &room_data.required_state);
373
374 let is_new_room = !state_store.room_exists(room_id);
376
377 let invite_state_events = room_data
378 .invite_state
379 .as_ref()
380 .map(|events| processors::state_events::stripped::collect(context, events));
381
382 #[allow(unused_mut)] let (mut room, mut room_info, invited_room, knocked_room) = self
384 .process_sliding_sync_room_membership(
385 context,
386 &state_events,
387 &invite_state_events,
388 state_store,
389 user_id,
390 room_id,
391 );
392
393 room_info.mark_state_partially_synced();
394 room_info.handle_encryption_state(requested_required_states);
395
396 #[cfg_attr(not(feature = "e2e-encryption"), allow(unused))]
397 let new_user_ids = processors::state_events::dispatch_and_get_new_users(
398 context,
399 (&raw_state_events, &state_events),
400 &mut room_info,
401 ambiguity_cache,
402 )
403 .await?;
404
405 let push_rules = self.get_push_rules(global_account_data_processor).await?;
406
407 if let Some(invite_state) = invite_state_events {
409 self.handle_invited_state(
410 context,
411 &room,
412 invite_state,
413 &push_rules,
414 &mut room_info,
415 notifications,
416 )
417 .await?;
418 }
419
420 process_room_properties(context, room_id, room_data, &mut room_info, is_new_room);
421
422 let timeline = processors::timeline::build(
423 context,
424 &room,
425 &mut room_info,
426 processors::timeline::builder::Timeline::from(room_data),
427 processors::timeline::builder::Notification::new(
428 &push_rules,
429 notifications,
430 &self.state_store,
431 ),
432 #[cfg(feature = "e2e-encryption")]
433 processors::timeline::builder::E2EE::new(
434 self.olm_machine().await.as_ref(),
435 self.decryption_trust_requirement,
436 self.handle_verification_events,
437 ),
438 )
439 .await?;
440
441 #[cfg(feature = "e2e-encryption")]
444 cache_latest_events(
445 &room,
446 &mut room_info,
447 &timeline.events,
448 Some(&context.state_changes),
449 Some(state_store),
450 )
451 .await;
452
453 #[cfg(feature = "e2e-encryption")]
454 processors::e2ee::tracked_users::update_or_set_if_room_is_newly_encrypted(
455 context,
456 self.olm_machine().await.as_ref(),
457 &new_user_ids,
458 room_info.encryption_state(),
459 room.encryption_state(),
460 room_id,
461 state_store,
462 )
463 .await?;
464
465 let notification_count = room_data.unread_notifications.clone().into();
466 room_info.update_notification_count(notification_count);
467
468 let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
469 let room_account_data = rooms_account_data.get(room_id).cloned();
470
471 match room_info.state() {
472 RoomState::Joined => {
473 let ephemeral = Vec::new();
477
478 Ok((
479 room_info,
480 Some(JoinedRoomUpdate::new(
481 timeline,
482 raw_state_events,
483 room_account_data.unwrap_or_default(),
484 ephemeral,
485 notification_count,
486 ambiguity_changes,
487 )),
488 None,
489 None,
490 None,
491 ))
492 }
493
494 RoomState::Left | RoomState::Banned => Ok((
495 room_info,
496 None,
497 Some(LeftRoomUpdate::new(
498 timeline,
499 raw_state_events,
500 room_account_data.unwrap_or_default(),
501 ambiguity_changes,
502 )),
503 None,
504 None,
505 )),
506
507 RoomState::Invited => Ok((room_info, None, None, invited_room, None)),
508
509 RoomState::Knocked => Ok((room_info, None, None, None, knocked_room)),
510 }
511 }
512
513 fn process_sliding_sync_room_membership(
519 &self,
520 context: &mut processors::Context,
521 state_events: &[AnySyncStateEvent],
522 invite_state_events: &Option<(Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>)>,
523 store: &BaseStateStore,
524 user_id: &UserId,
525 room_id: &RoomId,
526 ) -> (Room, RoomInfo, Option<InvitedRoom>, Option<KnockedRoom>) {
527 if let Some(state_events) = invite_state_events {
528 let room = store.get_or_create_room(
529 room_id,
530 RoomState::Invited,
531 self.room_info_notable_update_sender.clone(),
532 );
533 let mut room_info = room.clone_info();
534
535 let membership_event_content = state_events.1.iter().find_map(|event| {
538 if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
539 if membership_event.state_key == user_id {
540 return Some(membership_event.content.clone());
541 }
542 }
543 None
544 });
545
546 if let Some(membership_event_content) = membership_event_content {
547 if membership_event_content.membership == MembershipState::Knock {
548 room_info.mark_as_knocked();
550 let raw_events = state_events.0.clone();
551 let knock_state = assign!(v3::KnockState::default(), { events: raw_events });
552 let knocked_room =
553 assign!(KnockedRoom::default(), { knock_state: knock_state });
554 return (room, room_info, None, Some(knocked_room));
555 }
556 }
557
558 room_info.mark_as_invited();
560 let raw_events = state_events.0.clone();
561 let invited_room = InvitedRoom::from(v3::InviteState::from(raw_events));
562 (room, room_info, Some(invited_room), None)
563 } else {
564 let room = store.get_or_create_room(
565 room_id,
566 RoomState::Joined,
567 self.room_info_notable_update_sender.clone(),
568 );
569 let mut room_info = room.clone_info();
570
571 room_info.mark_as_joined();
576
577 self.handle_own_room_membership(context, state_events, &mut room_info);
583
584 (room, room_info, None, None)
585 }
586 }
587
588 fn handle_own_room_membership(
591 &self,
592 context: &mut processors::Context,
593 state_events: &[AnySyncStateEvent],
594 room_info: &mut RoomInfo,
595 ) {
596 let Some(meta) = self.session_meta() else {
597 return;
598 };
599
600 for event in state_events.iter().rev() {
604 if let AnySyncStateEvent::RoomMember(member) = &event {
605 if member.state_key() == meta.user_id.as_str() {
608 let new_state: RoomState = member.membership().into();
609 if new_state != room_info.state() {
610 room_info.set_state(new_state);
611 context
613 .room_info_notable_updates
614 .entry(room_info.room_id.to_owned())
615 .or_default()
616 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
617 }
618 break;
619 }
620 }
621 }
622 }
623}
624
625#[cfg(feature = "e2e-encryption")]
633async fn cache_latest_events(
634 room: &Room,
635 room_info: &mut RoomInfo,
636 events: &[TimelineEvent],
637 changes: Option<&StateChanges>,
638 store: Option<&BaseStateStore>,
639) {
640 use crate::{
641 deserialized_responses::DisplayName, store::ambiguity_map::is_display_name_ambiguous,
642 };
643
644 let mut encrypted_events =
645 Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
646
647 let power_levels_from_changes = || {
649 let state_changes = changes?.state.get(room_info.room_id())?;
650 let room_power_levels_state =
651 state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
652 match room_power_levels_state.deserialize().ok()? {
653 AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
654 _ => None,
655 }
656 };
657
658 let power_levels = match power_levels_from_changes() {
660 Some(power_levels) => Some(power_levels),
661 None => room.power_levels().await.ok(),
662 };
663
664 let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
665
666 for event in events.iter().rev() {
667 if let Ok(timeline_event) = event.raw().deserialize() {
668 match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
669 PossibleLatestEvent::YesRoomMessage(_)
670 | PossibleLatestEvent::YesPoll(_)
671 | PossibleLatestEvent::YesCallInvite(_)
672 | PossibleLatestEvent::YesCallNotify(_)
673 | PossibleLatestEvent::YesSticker(_)
674 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
675 let mut sender_profile = None;
683 let mut sender_name_is_ambiguous = None;
684
685 if let Some(changes) = changes {
688 sender_profile = changes
689 .profiles
690 .get(room.room_id())
691 .and_then(|profiles_by_user| {
692 profiles_by_user.get(timeline_event.sender())
693 })
694 .cloned();
695
696 if let Some(sender_profile) = sender_profile.as_ref() {
697 sender_name_is_ambiguous = sender_profile
698 .as_original()
699 .and_then(|profile| profile.content.displayname.as_ref())
700 .and_then(|display_name| {
701 let display_name = DisplayName::new(display_name);
702
703 changes.ambiguity_maps.get(room.room_id()).and_then(
704 |map_for_room| {
705 map_for_room.get(&display_name).map(|users| {
706 is_display_name_ambiguous(&display_name, users)
707 })
708 },
709 )
710 });
711 }
712 }
713
714 if sender_profile.is_none() {
716 if let Some(store) = store {
717 sender_profile = store
718 .get_profile(room.room_id(), timeline_event.sender())
719 .await
720 .ok()
721 .flatten();
722
723 }
726 }
727
728 let latest_event = Box::new(LatestEvent::new_with_sender_details(
729 event.clone(),
730 sender_profile,
731 sender_name_is_ambiguous,
732 ));
733
734 room_info.latest_event = Some(latest_event);
736 room.latest_encrypted_events.write().unwrap().clear();
739 break;
742 }
743 PossibleLatestEvent::NoEncrypted => {
744 if encrypted_events.len() < encrypted_events.capacity() {
750 encrypted_events.push(event.raw().clone());
751 }
752 }
753 _ => {
754 }
756 }
757 } else {
758 warn!(
759 "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
760 event.event_id().expect("Event has no ID!")
761 );
762 }
763 }
764
765 room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
768}
769
770fn process_room_properties(
771 context: &mut processors::Context,
772 room_id: &RoomId,
773 room_data: &http::response::Room,
774 room_info: &mut RoomInfo,
775 is_new_room: bool,
776) {
777 match &room_data.avatar {
783 JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
785 JsOption::Null => room_info.update_avatar(None),
787 JsOption::Undefined => {}
789 }
790
791 if let Some(count) = room_data.joined_count {
795 room_info.update_joined_member_count(count.into());
796 }
797 if let Some(count) = room_data.invited_count {
798 room_info.update_invited_member_count(count.into());
799 }
800
801 if let Some(heroes) = &room_data.heroes {
802 room_info.update_heroes(
803 heroes
804 .iter()
805 .map(|hero| RoomHero {
806 user_id: hero.user_id.clone(),
807 display_name: hero.name.clone(),
808 avatar_url: hero.avatar.clone(),
809 })
810 .collect(),
811 );
812 }
813
814 room_info.set_prev_batch(room_data.prev_batch.as_deref());
815
816 if room_data.limited {
817 room_info.mark_members_missing();
818 }
819
820 if let Some(recency_stamp) = &room_data.bump_stamp {
821 let recency_stamp: u64 = (*recency_stamp).into();
822
823 if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
824 room_info.update_recency_stamp(recency_stamp);
825
826 if !is_new_room {
830 context
831 .room_info_notable_updates
832 .entry(room_id.to_owned())
833 .or_default()
834 .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
835 }
836 }
837 }
838}
839
840#[cfg(all(test, not(target_family = "wasm")))]
841mod tests {
842 use std::collections::{BTreeMap, HashSet};
843 #[cfg(feature = "e2e-encryption")]
844 use std::sync::{Arc, RwLock as SyncRwLock};
845
846 use assert_matches::assert_matches;
847 use matrix_sdk_common::deserialized_responses::TimelineEvent;
848 #[cfg(feature = "e2e-encryption")]
849 use matrix_sdk_common::{
850 deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
851 ring_buffer::RingBuffer,
852 };
853 use matrix_sdk_test::async_test;
854 use ruma::{
855 api::client::sync::sync_events::UnreadNotificationsCount,
856 assign, event_id,
857 events::{
858 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
859 room::{
860 avatar::RoomAvatarEventContent,
861 canonical_alias::RoomCanonicalAliasEventContent,
862 encryption::RoomEncryptionEventContent,
863 member::{MembershipState, RoomMemberEventContent},
864 message::SyncRoomMessageEvent,
865 name::RoomNameEventContent,
866 pinned_events::RoomPinnedEventsEventContent,
867 },
868 AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
869 StateEventContent, StateEventType,
870 },
871 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
872 serde::Raw,
873 uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
874 };
875 use serde_json::json;
876
877 #[cfg(feature = "e2e-encryption")]
878 use super::cache_latest_events;
879 use super::http;
880 use crate::{
881 rooms::normal::{RoomHero, RoomInfoNotableUpdateReasons},
882 test_utils::logged_in_base_client,
883 BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
884 };
885 #[cfg(feature = "e2e-encryption")]
886 use crate::{store::MemoryStore, Room};
887
888 #[async_test]
889 async fn test_notification_count_set() {
890 let client = logged_in_base_client(None).await;
891
892 let mut response = http::Response::new("42".to_owned());
893 let room_id = room_id!("!room:example.org");
894 let count = assign!(UnreadNotificationsCount::default(), {
895 highlight_count: Some(uint!(13)),
896 notification_count: Some(uint!(37)),
897 });
898
899 response.rooms.insert(
900 room_id.to_owned(),
901 assign!(http::response::Room::new(), {
902 unread_notifications: count.clone()
903 }),
904 );
905
906 let sync_response = client
907 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
908 .await
909 .expect("Failed to process sync");
910
911 let room = sync_response.rooms.join.get(room_id).unwrap();
913 assert_eq!(room.unread_notifications, count.clone().into());
914
915 let room = client.get_room(room_id).expect("found room");
917 assert_eq!(room.unread_notification_counts(), count.into());
918 }
919
920 #[async_test]
921 async fn test_can_process_empty_sliding_sync_response() {
922 let client = logged_in_base_client(None).await;
923 let empty_response = http::Response::new("5".to_owned());
924 client
925 .process_sliding_sync(&empty_response, &(), &RequestedRequiredStates::default())
926 .await
927 .expect("Failed to process sync");
928 }
929
930 #[async_test]
931 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
932 let client = logged_in_base_client(None).await;
934 let room_id = room_id!("!r:e.uk");
935
936 let mut room = http::response::Room::new();
939 room.joined_count = Some(uint!(41));
940 let response = response_with_room(room_id, room);
941 let sync_resp = client
942 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
943 .await
944 .expect("Failed to process sync");
945
946 let client_room = client.get_room(room_id).expect("No room found");
948 assert_eq!(client_room.room_id(), room_id);
949 assert_eq!(client_room.joined_members_count(), 41);
950 assert_eq!(client_room.state(), RoomState::Joined);
951
952 assert!(sync_resp.rooms.join.contains_key(room_id));
954 assert!(!sync_resp.rooms.leave.contains_key(room_id));
955 assert!(!sync_resp.rooms.invite.contains_key(room_id));
956 }
957
958 #[async_test]
959 async fn test_missing_room_name_event() {
960 let client = logged_in_base_client(None).await;
962 let room_id = room_id!("!r:e.uk");
963
964 let mut room = http::response::Room::new();
967 room.name = Some("little room".to_owned());
968 let response = response_with_room(room_id, room);
969 let sync_resp = client
970 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
971 .await
972 .expect("Failed to process sync");
973
974 let client_room = client.get_room(room_id).expect("No room found");
976 assert!(client_room.name().is_none());
977 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "Empty Room");
978 assert_eq!(client_room.state(), RoomState::Joined);
979
980 assert!(sync_resp.rooms.join.contains_key(room_id));
982 assert!(!sync_resp.rooms.leave.contains_key(room_id));
983 assert!(!sync_resp.rooms.invite.contains_key(room_id));
984 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
985 }
986
987 #[async_test]
988 async fn test_room_name_event() {
989 let client = logged_in_base_client(None).await;
991 let room_id = room_id!("!r:e.uk");
992
993 let mut room = http::response::Room::new();
996
997 room.name = Some("little room".to_owned());
998 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
999
1000 let response = response_with_room(room_id, room);
1001 client
1002 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1003 .await
1004 .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 = client
1027 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1028 .await
1029 .expect("Failed to process sync");
1030
1031 let client_room = client.get_room(room_id).expect("No room found");
1033 assert!(client_room.name().is_none());
1034
1035 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "w");
1037
1038 assert_eq!(client_room.state(), RoomState::Invited);
1039
1040 assert!(!sync_resp.rooms.join.contains_key(room_id));
1042 assert!(!sync_resp.rooms.leave.contains_key(room_id));
1043 assert!(sync_resp.rooms.invite.contains_key(room_id));
1044 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1045 }
1046
1047 #[async_test]
1048 async fn test_invited_room_name_event() {
1049 let client = logged_in_base_client(None).await;
1051 let room_id = room_id!("!r:e.uk");
1052 let user_id = user_id!("@w:e.uk");
1053 let inviter = user_id!("@john:mastodon.org");
1054
1055 let mut room = http::response::Room::new();
1058
1059 set_room_invited(&mut room, inviter, user_id);
1060
1061 room.name = Some("name from sliding sync response".to_owned());
1062 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1063
1064 let response = response_with_room(room_id, room);
1065 client
1066 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1067 .await
1068 .expect("Failed to process sync");
1069
1070 let client_room = client.get_room(room_id).expect("No room found");
1072 assert_eq!(client_room.name().as_deref(), Some("The Name"));
1073 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1074 }
1075
1076 #[async_test]
1077 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
1078 let client = logged_in_base_client(None).await;
1080 let room_id = room_id!("!r:e.uk");
1081 let user_id = client.session_meta().unwrap().user_id.to_owned();
1082
1083 let mut room = http::response::Room::new();
1086 set_room_knocked(&mut room, &user_id);
1087
1088 let response = response_with_room(room_id, room);
1089 client
1090 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1091 .await
1092 .expect("Failed to process sync");
1093
1094 let client_room = client.get_room(room_id).expect("No room found");
1096 assert_eq!(client_room.state(), RoomState::Knocked);
1097 }
1098
1099 #[async_test]
1100 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
1101 ) {
1102 let client = logged_in_base_client(None).await;
1104 let room_id = room_id!("!r:e.uk");
1105 let user_id = user_id!("@w:e.uk");
1106
1107 let mut room = http::response::Room::new();
1109 set_room_knocked(&mut room, user_id);
1110
1111 let response = response_with_room(room_id, room);
1112 client
1113 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1114 .await
1115 .expect("Failed to process sync");
1116
1117 let client_room = client.get_room(room_id).expect("No room found");
1120 assert_eq!(client_room.state(), RoomState::Invited);
1121 }
1122
1123 #[async_test]
1124 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
1125 ) {
1126 let client = logged_in_base_client(None).await;
1128 let room_id = room_id!("!r:e.uk");
1129 let user_id = client.session_meta().unwrap().user_id.to_owned();
1130
1131 let mut room = http::response::Room::new();
1133 let event = Raw::new(&json!({
1134 "type": "m.room.member",
1135 "sender": user_id,
1136 "content": {
1137 "is_direct": true,
1138 "membership": "join",
1139 },
1140 "state_key": user_id,
1141 }))
1142 .expect("Failed to make raw event")
1143 .cast();
1144 room.invite_state = Some(vec![event]);
1145
1146 let response = response_with_room(room_id, room);
1147 client
1148 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1149 .await
1150 .expect("Failed to process sync");
1151
1152 let client_room = client.get_room(room_id).expect("No room found");
1154 assert_eq!(client_room.state(), RoomState::Invited);
1155 }
1156
1157 #[async_test]
1158 async fn test_left_a_room_from_required_state_event() {
1159 let client = logged_in_base_client(None).await;
1161 let room_id = room_id!("!r:e.uk");
1162 let user_id = user_id!("@u:e.uk");
1163
1164 let mut room = http::response::Room::new();
1166 set_room_joined(&mut room, user_id);
1167 let response = response_with_room(room_id, room);
1168 client
1169 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1170 .await
1171 .expect("Failed to process sync");
1172 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1173
1174 let mut room = http::response::Room::new();
1176 set_room_left(&mut room, user_id);
1177 let response = response_with_room(room_id, room);
1178 let sync_resp = client
1179 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1180 .await
1181 .expect("Failed to process sync");
1182
1183 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1185
1186 assert!(!sync_resp.rooms.join.contains_key(room_id));
1188 assert!(sync_resp.rooms.leave.contains_key(room_id));
1189 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1190 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1191 }
1192
1193 #[async_test]
1194 async fn test_kick_or_ban_updates_room_to_left() {
1195 for membership in [MembershipState::Leave, MembershipState::Ban] {
1196 let room_id = room_id!("!r:e.uk");
1197 let user_a_id = user_id!("@a:e.uk");
1198 let user_b_id = user_id!("@b:e.uk");
1199 let client = logged_in_base_client(Some(user_a_id)).await;
1200
1201 let mut room = http::response::Room::new();
1203 set_room_joined(&mut room, user_a_id);
1204 let response = response_with_room(room_id, room);
1205 client
1206 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1207 .await
1208 .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 room.required_state.push(make_state_event(
1214 user_b_id,
1215 user_a_id.as_str(),
1216 RoomMemberEventContent::new(membership.clone()),
1217 None,
1218 ));
1219 let response = response_with_room(room_id, room);
1220 let sync_resp = client
1221 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1222 .await
1223 .expect("Failed to process sync");
1224
1225 match membership {
1226 MembershipState::Leave => {
1227 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1229 }
1230 MembershipState::Ban => {
1231 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1233 }
1234 _ => panic!("Unexpected membership state found: {membership}"),
1235 }
1236
1237 assert!(!sync_resp.rooms.join.contains_key(room_id));
1239 assert!(sync_resp.rooms.leave.contains_key(room_id));
1240 assert!(!sync_resp.rooms.invite.contains_key(room_id));
1241 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1242 }
1243 }
1244
1245 #[async_test]
1246 async fn test_left_a_room_from_timeline_state_event() {
1247 let client = logged_in_base_client(None).await;
1249 let room_id = room_id!("!r:e.uk");
1250 let user_id = user_id!("@u:e.uk");
1251
1252 let mut room = http::response::Room::new();
1254 set_room_joined(&mut room, user_id);
1255 let response = response_with_room(room_id, room);
1256 client
1257 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1258 .await
1259 .expect("Failed to process sync");
1260 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1261
1262 let mut room = http::response::Room::new();
1264 set_room_left_as_timeline_event(&mut room, user_id);
1265 let response = response_with_room(room_id, room);
1266 client
1267 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1268 .await
1269 .expect("Failed to process sync");
1270
1271 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1273 }
1274
1275 #[async_test]
1276 async fn test_can_be_reinvited_to_a_left_room() {
1277 let client = logged_in_base_client(None).await;
1281 let room_id = room_id!("!r:e.uk");
1282 let user_id = user_id!("@u:e.uk");
1283
1284 let mut room = http::response::Room::new();
1286 set_room_joined(&mut room, user_id);
1287 let response = response_with_room(room_id, room);
1288 client
1289 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1290 .await
1291 .expect("Failed to process sync");
1292 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1294
1295 let mut room = http::response::Room::new();
1297 set_room_left(&mut room, user_id);
1298 let response = response_with_room(room_id, room);
1299 client
1300 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1301 .await
1302 .expect("Failed to process sync");
1303 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1305
1306 let mut room = http::response::Room::new();
1308 set_room_invited(&mut room, user_id, user_id);
1309 let response = response_with_room(room_id, room);
1310 client
1311 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1312 .await
1313 .expect("Failed to process sync");
1314
1315 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1317 }
1318
1319 #[async_test]
1320 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1321 let room_id = room_id!("!r:e.uk");
1322 let user_a_id = user_id!("@a:e.uk");
1323 let user_b_id = user_id!("@b:e.uk");
1324
1325 let client = logged_in_base_client(None).await;
1327 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1328
1329 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1331 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1332
1333 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1335
1336 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1340 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1341 }
1342
1343 #[async_test]
1344 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
1345 ) {
1346 let room_id = room_id!("!r:e.uk");
1347 let user_a_id = user_id!("@a:e.uk");
1348 let user_b_id = user_id!("@b:e.uk");
1349
1350 let client = logged_in_base_client(None).await;
1352 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1353
1354 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1356 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1357
1358 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1360
1361 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1365 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1366 }
1367
1368 #[async_test]
1369 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1370 let room_id = room_id!("!r:bar.org");
1371 let user_a_id = user_id!("@a:bar.org");
1372 let user_b_id = user_id!("@b:bar.org");
1373
1374 let client = logged_in_base_client(None).await;
1376 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1377
1378 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1380
1381 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1383 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1384
1385 let room = client.get_room(room_id).unwrap();
1386
1387 assert_eq!(room.active_members_count(), 2);
1388 assert_eq!(room.joined_members_count(), 2);
1389 assert_eq!(room.invited_members_count(), 0);
1390 }
1391
1392 #[async_test]
1393 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1394 let room_id = room_id!("!r:bar.org");
1395 let user_a_id = user_id!("@a:bar.org");
1396 let user_b_id = user_id!("@b:bar.org");
1397
1398 let client = logged_in_base_client(None).await;
1400 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1401
1402 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1404
1405 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1407 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1408
1409 let room = client.get_room(room_id).unwrap();
1410
1411 assert_eq!(room.active_members_count(), 2);
1412 assert_eq!(room.joined_members_count(), 1);
1413 assert_eq!(room.invited_members_count(), 1);
1414 }
1415
1416 #[async_test]
1417 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1418 let client = logged_in_base_client(None).await;
1420 let room_id = room_id!("!r:e.uk");
1421
1422 let room = {
1424 let mut room = http::response::Room::new();
1425 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1426
1427 room
1428 };
1429 let response = response_with_room(room_id, room);
1430 client
1431 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1432 .await
1433 .expect("Failed to process sync");
1434
1435 let client_room = client.get_room(room_id).expect("No room found");
1437 assert_eq!(
1438 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1439 "med1"
1440 );
1441 }
1442
1443 #[async_test]
1444 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1445 let client = logged_in_base_client(None).await;
1447 let room_id = room_id!("!r:e.uk");
1448
1449 let room = {
1453 let mut room = http::response::Room::new();
1454 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1455
1456 room
1457 };
1458 let response = response_with_room(room_id, room);
1459 client
1460 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1461 .await
1462 .expect("Failed to process sync");
1463
1464 let client_room = client.get_room(room_id).expect("No room found");
1466 assert_eq!(
1467 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1468 "med1"
1469 );
1470
1471 let room = http::response::Room::new();
1475 let response = response_with_room(room_id, room);
1476 client
1477 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1478 .await
1479 .expect("Failed to process sync");
1480
1481 let client_room = client.get_room(room_id).expect("No room found");
1483 assert_eq!(
1484 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1485 "med1"
1486 );
1487
1488 let room = {
1492 let mut room = http::response::Room::new();
1493 room.avatar = JsOption::Null;
1494
1495 room
1496 };
1497 let response = response_with_room(room_id, room);
1498 client
1499 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1500 .await
1501 .expect("Failed to process sync");
1502
1503 let client_room = client.get_room(room_id).expect("No room found");
1505 assert!(client_room.avatar_url().is_none());
1506 }
1507
1508 #[async_test]
1509 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1510 let client = logged_in_base_client(None).await;
1512 let room_id = room_id!("!r:e.uk");
1513 let user_id = user_id!("@u:e.uk");
1514
1515 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1517 let response = response_with_room(room_id, room);
1518 client
1519 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1520 .await
1521 .expect("Failed to process sync");
1522
1523 let client_room = client.get_room(room_id).expect("No room found");
1525 assert_eq!(
1526 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1527 "med1"
1528 );
1529 }
1530
1531 #[async_test]
1532 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1533 let client = logged_in_base_client(None).await;
1535 let room_id = room_id!("!r:e.uk");
1536 let user_id = user_id!("@u:e.uk");
1537
1538 let mut room = http::response::Room::new();
1540 set_room_invited(&mut room, user_id, user_id);
1541 let response = response_with_room(room_id, room);
1542 let sync_resp = client
1543 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1544 .await
1545 .expect("Failed to process sync");
1546
1547 let client_room = client.get_room(room_id).expect("No room found");
1549 assert_eq!(client_room.room_id(), room_id);
1550 assert_eq!(client_room.state(), RoomState::Invited);
1551
1552 assert!(!sync_resp.rooms.invite[room_id].invite_state.is_empty());
1554 assert!(!sync_resp.rooms.join.contains_key(room_id));
1555 }
1556
1557 #[async_test]
1558 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1559 let client = logged_in_base_client(None).await;
1561 let room_id = room_id!("!r:e.uk");
1562 let user_id = user_id!("@u:e.uk");
1563
1564 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1566 set_room_invited(&mut room, user_id, user_id);
1567 let response = response_with_room(room_id, room);
1568 client
1569 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1570 .await
1571 .expect("Failed to process sync");
1572
1573 let client_room = client.get_room(room_id).expect("No room found");
1575 assert_eq!(
1576 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1577 "med1"
1578 );
1579 }
1580
1581 #[async_test]
1582 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
1583 ) {
1584 let client = logged_in_base_client(None).await;
1586 let room_id = room_id!("!r:e.uk");
1587 let user_id = user_id!("@u:e.uk");
1588 let room_alias_id = room_alias_id!("#myroom:e.uk");
1589
1590 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1592 set_room_invited(&mut room, user_id, user_id);
1593 let response = response_with_room(room_id, room);
1594 client
1595 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1596 .await
1597 .expect("Failed to process sync");
1598
1599 let client_room = client.get_room(room_id).expect("No room found");
1601 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1602 }
1603
1604 #[async_test]
1605 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1606 let client = logged_in_base_client(None).await;
1608 let room_id = room_id!("!r:e.uk");
1609 let user_id = user_id!("@u:e.uk");
1610 let room_alias_id = room_alias_id!("#myroom:e.uk");
1611
1612 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1615 room.name = Some("This came from the server".to_owned());
1616 let response = response_with_room(room_id, room);
1617 client
1618 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1619 .await
1620 .expect("Failed to process sync");
1621
1622 let client_room = client.get_room(room_id).expect("No room found");
1624 assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "myroom");
1625 assert!(client_room.name().is_none());
1626 }
1627
1628 #[async_test]
1629 async fn test_compute_heroes_from_sliding_sync() {
1630 let client = logged_in_base_client(None).await;
1632 let room_id = room_id!("!r:e.uk");
1633 let gordon = user_id!("@gordon:e.uk").to_owned();
1634 let alice = user_id!("@alice:e.uk").to_owned();
1635
1636 let mut room = http::response::Room::new();
1639 room.heroes = Some(vec![
1640 assign!(http::response::Hero::new(gordon), {
1641 name: Some("Gordon".to_owned()),
1642 }),
1643 assign!(http::response::Hero::new(alice), {
1644 name: Some("Alice".to_owned()),
1645 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1646 }),
1647 ]);
1648 let response = response_with_room(room_id, room);
1649 let _sync_resp = client
1650 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1651 .await
1652 .expect("Failed to process sync");
1653
1654 let client_room = client.get_room(room_id).expect("No room found");
1656 assert_eq!(client_room.room_id(), room_id);
1657 assert_eq!(client_room.state(), RoomState::Joined);
1658
1659 assert_eq!(
1661 client_room.clone_info().summary.heroes(),
1662 &[
1663 RoomHero {
1664 user_id: owned_user_id!("@gordon:e.uk"),
1665 display_name: Some("Gordon".to_owned()),
1666 avatar_url: None
1667 },
1668 RoomHero {
1669 user_id: owned_user_id!("@alice:e.uk"),
1670 display_name: Some("Alice".to_owned()),
1671 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1672 },
1673 ]
1674 );
1675 }
1676
1677 #[async_test]
1678 async fn test_last_event_from_sliding_sync_is_cached() {
1679 let client = logged_in_base_client(None).await;
1681 let room_id = room_id!("!r:e.uk");
1682 let event_a = json!({
1683 "sender":"@alice:example.com",
1684 "type":"m.room.message",
1685 "event_id": "$ida",
1686 "origin_server_ts": 12344446,
1687 "content":{"body":"A", "msgtype": "m.text"}
1688 });
1689 let event_b = json!({
1690 "sender":"@alice:example.com",
1691 "type":"m.room.message",
1692 "event_id": "$idb",
1693 "origin_server_ts": 12344447,
1694 "content":{"body":"B", "msgtype": "m.text"}
1695 });
1696
1697 let events = &[event_a, event_b.clone()];
1699 let room = room_with_timeline(events);
1700 let response = response_with_room(room_id, room);
1701 client
1702 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1703 .await
1704 .expect("Failed to process sync");
1705
1706 let client_room = client.get_room(room_id).expect("No room found");
1708 assert_eq!(
1709 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1710 "$idb"
1711 );
1712 }
1713
1714 #[async_test]
1715 async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1716 let own_user_id = user_id!("@me:e.uk");
1717 let client = logged_in_base_client(Some(own_user_id)).await;
1719 let room_id = room_id!("!r:e.uk");
1720
1721 let power_levels = json!({
1723 "sender":"@alice:example.com",
1724 "state_key":"",
1725 "type":"m.room.power_levels",
1726 "event_id": "$idb",
1727 "origin_server_ts": 12344445,
1728 "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1729 "room_id": room_id,
1730 });
1731
1732 let knock_event = json!({
1734 "sender":"@alice:example.com",
1735 "state_key":"@alice:example.com",
1736 "type":"m.room.member",
1737 "event_id": "$ida",
1738 "origin_server_ts": 12344446,
1739 "content":{"membership": "knock"},
1740 "room_id": room_id,
1741 });
1742
1743 let events = &[knock_event];
1745 let mut room = room_with_timeline(events);
1746 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1747 let response = response_with_room(room_id, room);
1748 client
1749 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1750 .await
1751 .expect("Failed to process sync");
1752
1753 let client_room = client.get_room(room_id).expect("No room found");
1755 assert_eq!(
1756 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1757 "$ida"
1758 );
1759 }
1760
1761 #[async_test]
1762 async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1763 let own_user_id = user_id!("@me:e.uk");
1764 let client = logged_in_base_client(Some(own_user_id)).await;
1766 let room_id = room_id!("!r:e.uk");
1767
1768 let power_levels = json!({
1771 "sender":"@alice:example.com",
1772 "state_key":"",
1773 "type":"m.room.power_levels",
1774 "event_id": "$idb",
1775 "origin_server_ts": 12344445,
1776 "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1777 "room_id": room_id,
1778 });
1779
1780 let knock_event = json!({
1782 "sender":"@alice:example.com",
1783 "state_key":"@alice:example.com",
1784 "type":"m.room.member",
1785 "event_id": "$ida",
1786 "origin_server_ts": 12344446,
1787 "content":{"membership": "knock"},
1788 "room_id": room_id,
1789 });
1790
1791 let events = &[knock_event];
1793 let mut room = room_with_timeline(events);
1794 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1795 let response = response_with_room(room_id, room);
1796 client
1797 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1798 .await
1799 .expect("Failed to process sync");
1800
1801 let client_room = client.get_room(room_id).expect("No room found");
1803 assert!(client_room.latest_event().is_none());
1804 }
1805
1806 #[async_test]
1807 async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1808 let client = logged_in_base_client(None).await;
1810 let room_id = room_id!("!r:e.uk");
1811 let join_event = json!({
1813 "sender":"@alice:example.com",
1814 "state_key":"@alice:example.com",
1815 "type":"m.room.member",
1816 "event_id": "$ida",
1817 "origin_server_ts": 12344446,
1818 "content":{"membership": "join"},
1819 "room_id": room_id,
1820 });
1821
1822 let events = &[join_event];
1824 let room = room_with_timeline(events);
1825 let response = response_with_room(room_id, room);
1826 client
1827 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1828 .await
1829 .expect("Failed to process sync");
1830
1831 let client_room = client.get_room(room_id).expect("No room found");
1833 assert!(client_room.latest_event().is_none());
1834 }
1835
1836 #[async_test]
1837 async fn test_cached_latest_event_can_be_redacted() {
1838 let client = logged_in_base_client(None).await;
1840 let room_id = room_id!("!r:e.uk");
1841 let event_a = json!({
1842 "sender": "@alice:example.com",
1843 "type": "m.room.message",
1844 "event_id": "$ida",
1845 "origin_server_ts": 12344446,
1846 "content": { "body":"A", "msgtype": "m.text" },
1847 });
1848
1849 let room = room_with_timeline(&[event_a]);
1851 let response = response_with_room(room_id, room);
1852 client
1853 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1854 .await
1855 .expect("Failed to process sync");
1856
1857 let client_room = client.get_room(room_id).expect("No room found");
1859 assert_eq!(
1860 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1861 "$ida"
1862 );
1863
1864 let redaction = json!({
1865 "sender": "@alice:example.com",
1866 "type": "m.room.redaction",
1867 "event_id": "$idb",
1868 "redacts": "$ida",
1869 "origin_server_ts": 12344448,
1870 "content": {},
1871 });
1872
1873 let room = room_with_timeline(&[redaction]);
1875 let response = response_with_room(room_id, room);
1876 client
1877 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
1878 .await
1879 .expect("Failed to process sync");
1880
1881 let client_room = client.get_room(room_id).expect("No room found");
1883 let latest_event = client_room.latest_event().unwrap();
1884 assert_eq!(latest_event.event_id().unwrap(), "$ida");
1885
1886 assert_matches!(
1888 latest_event.event().raw().deserialize().unwrap(),
1889 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1890 SyncRoomMessageEvent::Redacted(_)
1891 ))
1892 );
1893 }
1894
1895 #[cfg(feature = "e2e-encryption")]
1896 #[async_test]
1897 async fn test_when_no_events_we_dont_cache_any() {
1898 let events = &[];
1899 let chosen = choose_event_to_cache(events).await;
1900 assert!(chosen.is_none());
1901 }
1902
1903 #[cfg(feature = "e2e-encryption")]
1904 #[async_test]
1905 async fn test_when_only_one_event_we_cache_it() {
1906 let event1 = make_event("m.room.message", "$1");
1907 let events = &[event1.clone()];
1908 let chosen = choose_event_to_cache(events).await;
1909 assert_eq!(ev_id(chosen), rawev_id(event1));
1910 }
1911
1912 #[cfg(feature = "e2e-encryption")]
1913 #[async_test]
1914 async fn test_with_multiple_events_we_cache_the_last_one() {
1915 let event1 = make_event("m.room.message", "$1");
1916 let event2 = make_event("m.room.message", "$2");
1917 let events = &[event1, event2.clone()];
1918 let chosen = choose_event_to_cache(events).await;
1919 assert_eq!(ev_id(chosen), rawev_id(event2));
1920 }
1921
1922 #[cfg(feature = "e2e-encryption")]
1923 #[async_test]
1924 async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1925 let event1 = make_event("m.room.message", "$1");
1926 let event2 = make_event("m.room.message", "$2");
1927 let event3 = make_event("m.room.powerlevels", "$3");
1928 let event4 = make_event("m.room.powerlevels", "$5");
1929 let events = &[event1, event2.clone(), event3, event4];
1930 let chosen = choose_event_to_cache(events).await;
1931 assert_eq!(ev_id(chosen), rawev_id(event2));
1932 }
1933
1934 #[cfg(feature = "e2e-encryption")]
1935 #[async_test]
1936 async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1937 let event1 = make_event("m.room.power_levels", "$1");
1938 let events = &[event1];
1939 let chosen = choose_event_to_cache(events).await;
1940 assert!(chosen.is_none());
1941 }
1942
1943 #[cfg(feature = "e2e-encryption")]
1944 #[async_test]
1945 async fn test_cache_encrypted_events_that_are_after_latest_message() {
1946 let event1 = make_event("m.room.message", "$1");
1948 let event2 = make_event("m.room.message", "$2");
1949 let event3 = make_encrypted_event("$3");
1950 let event4 = make_encrypted_event("$4");
1951 let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
1952
1953 let room = make_room();
1955 let mut room_info = room.clone_info();
1956 cache_latest_events(&room, &mut room_info, events, None, None).await;
1957
1958 assert_eq!(
1960 ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
1961 rawev_id(event2.clone())
1962 );
1963
1964 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1965 assert_eq!(
1966 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1967 rawev_id(event2)
1968 );
1969
1970 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
1972 }
1973
1974 #[cfg(feature = "e2e-encryption")]
1975 #[async_test]
1976 async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
1977 let event1 = make_encrypted_event("$1");
1979 let event2 = make_event("m.room.message", "$2");
1980 let event3 = make_encrypted_event("$3");
1981 let events = &[event1, event2.clone(), event3.clone()];
1982
1983 let room = make_room();
1985 let mut room_info = room.clone_info();
1986 cache_latest_events(&room, &mut room_info, events, None, None).await;
1987 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1988
1989 assert_eq!(
1991 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1992 rawev_id(event2)
1993 );
1994
1995 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
1997 }
1998
1999 #[cfg(feature = "e2e-encryption")]
2000 #[async_test]
2001 async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
2002 let event1 = make_event("m.room.message", "$1");
2005 let event2 = make_event("m.room.message", "$2");
2006 let event3 = make_encrypted_event("$3");
2007 let event4 = make_event("m.read", "$4");
2008 let event5 = make_encrypted_event("$5");
2009 let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
2010
2011 let room = make_room();
2013 let mut room_info = room.clone_info();
2014 cache_latest_events(&room, &mut room_info, events, None, None).await;
2015 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2016
2017 assert_eq!(
2019 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2020 rawev_id(event2)
2021 );
2022
2023 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
2025 }
2026
2027 #[cfg(feature = "e2e-encryption")]
2028 #[async_test]
2029 async fn test_only_store_the_max_number_of_encrypted_events() {
2030 let evente = make_event("m.room.message", "$e");
2033 let eventd = make_event("m.room.message", "$d");
2034 let eventc = make_encrypted_event("$c");
2035 let event9 = make_encrypted_event("$9");
2036 let event8 = make_encrypted_event("$8");
2037 let event7 = make_encrypted_event("$7");
2038 let eventb = make_event("m.read", "$b");
2039 let event6 = make_encrypted_event("$6");
2040 let event5 = make_encrypted_event("$5");
2041 let event4 = make_encrypted_event("$4");
2042 let event3 = make_encrypted_event("$3");
2043 let event2 = make_encrypted_event("$2");
2044 let eventa = make_event("m.read", "$a");
2045 let event1 = make_encrypted_event("$1");
2046 let event0 = make_encrypted_event("$0");
2047 let events = &[
2048 evente,
2049 eventd.clone(),
2050 eventc,
2051 event9.clone(),
2052 event8.clone(),
2053 event7.clone(),
2054 eventb,
2055 event6.clone(),
2056 event5.clone(),
2057 event4.clone(),
2058 event3.clone(),
2059 event2.clone(),
2060 eventa,
2061 event1.clone(),
2062 event0.clone(),
2063 ];
2064
2065 let room = make_room();
2067 let mut room_info = room.clone_info();
2068 cache_latest_events(&room, &mut room_info, events, None, None).await;
2069 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2070
2071 assert_eq!(
2073 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
2074 rawev_id(eventd)
2075 );
2076
2077 assert_eq!(
2079 rawevs_ids(&room.latest_encrypted_events),
2080 evs_ids(&[
2081 event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
2082 ])
2083 );
2084 }
2085
2086 #[cfg(feature = "e2e-encryption")]
2087 #[async_test]
2088 async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
2089 let room = make_room();
2091 let mut room_info = room.clone_info();
2092 cache_latest_events(
2093 &room,
2094 &mut room_info,
2095 &[
2096 make_encrypted_event("$0"),
2097 make_encrypted_event("$1"),
2098 make_encrypted_event("$2"),
2099 make_encrypted_event("$3"),
2100 make_encrypted_event("$4"),
2101 make_encrypted_event("$5"),
2102 make_encrypted_event("$6"),
2103 make_encrypted_event("$7"),
2104 make_encrypted_event("$8"),
2105 make_encrypted_event("$9"),
2106 ],
2107 None,
2108 None,
2109 )
2110 .await;
2111 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2112
2113 assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
2115
2116 let eventa = make_encrypted_event("$a");
2118 let mut room_info = room.clone_info();
2119 cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
2120 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2121
2122 assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
2124
2125 assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
2127 }
2128
2129 #[cfg(feature = "e2e-encryption")]
2130 #[async_test]
2131 async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
2132 let room = make_room();
2134 let mut room_info = room.clone_info();
2135 cache_latest_events(
2136 &room,
2137 &mut room_info,
2138 &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
2139 None,
2140 None,
2141 )
2142 .await;
2143 room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
2144
2145 let eventa = make_event("m.room.message", "$a");
2147 let eventb = make_encrypted_event("$b");
2148 cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
2149 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2150
2151 assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
2153
2154 assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
2156 }
2157
2158 #[async_test]
2159 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2160 let client = logged_in_base_client(None).await;
2162 let room_id = room_id!("!r:e.uk");
2163
2164 let room = assign!(http::response::Room::new(), {
2166 bump_stamp: Some(42u32.into()),
2167 });
2168 let response = response_with_room(room_id, room);
2169 client
2170 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2171 .await
2172 .expect("Failed to process sync");
2173
2174 let client_room = client.get_room(room_id).expect("No room found");
2176 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2177 }
2178
2179 #[async_test]
2180 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2181 let client = logged_in_base_client(None).await;
2183 let room_id = room_id!("!r:e.uk");
2184
2185 {
2186 let room = assign!(http::response::Room::new(), {
2188 bump_stamp: Some(42u32.into()),
2189 });
2190 let response = response_with_room(room_id, room);
2191 client
2192 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2193 .await
2194 .expect("Failed to process sync");
2195
2196 let client_room = client.get_room(room_id).expect("No room found");
2198 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2199 }
2200
2201 {
2202 let room = assign!(http::response::Room::new(), {
2204 bump_stamp: None,
2205 });
2206 let response = response_with_room(room_id, room);
2207 client
2208 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2209 .await
2210 .expect("Failed to process sync");
2211
2212 let client_room = client.get_room(room_id).expect("No room found");
2214 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2215 }
2216
2217 {
2218 let room = assign!(http::response::Room::new(), {
2221 bump_stamp: Some(153u32.into()),
2222 });
2223 let response = response_with_room(room_id, room);
2224 client
2225 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2226 .await
2227 .expect("Failed to process sync");
2228
2229 let client_room = client.get_room(room_id).expect("No room found");
2231 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
2232 }
2233 }
2234
2235 #[async_test]
2236 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2237 let client = logged_in_base_client(None).await;
2239 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2240 let room_id = room_id!("!r:e.uk");
2241
2242 let room = assign!(http::response::Room::new(), {
2244 bump_stamp: Some(42u32.into()),
2245 });
2246 let response = response_with_room(room_id, room);
2247 client
2248 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2249 .await
2250 .expect("Failed to process sync");
2251
2252 assert_matches!(
2255 room_info_notable_update_stream.recv().await,
2256 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2257 assert_eq!(received_room_id, room_id);
2258 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2259 }
2260 );
2261
2262 let room = assign!(http::response::Room::new(), {
2264 bump_stamp: Some(43u32.into()),
2265 });
2266 let response = response_with_room(room_id, room);
2267 client
2268 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2269 .await
2270 .expect("Failed to process sync");
2271
2272 assert_matches!(
2274 room_info_notable_update_stream.recv().await,
2275 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2276 assert_eq!(received_room_id, room_id);
2277 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2278 }
2279 );
2280 }
2281
2282 #[async_test]
2283 async fn test_read_receipt_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
2288 let room_id = room_id!("!r:e.uk");
2290 let room = http::response::Room::new();
2291 let response = response_with_room(room_id, room);
2292 client
2293 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2294 .await
2295 .expect("Failed to process sync");
2296
2297 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::READ_RECEIPT));
2303 }
2304 );
2305
2306 let room_id = room_id!("!r:e.uk");
2309 let events = vec![
2310 make_raw_event("m.room.message", "$3"),
2311 make_raw_event("m.room.message", "$4"),
2312 make_raw_event("m.read", "$5"),
2313 ];
2314 let room = assign!(http::response::Room::new(), {
2315 timeline: events,
2316 });
2317 let response = response_with_room(room_id, room);
2318 client
2319 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2320 .await
2321 .expect("Failed to process sync");
2322
2323 assert_matches!(
2325 room_info_notable_update_stream.recv().await,
2326 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2327 assert_eq!(received_room_id, room_id);
2328 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2329 }
2330 );
2331 }
2332
2333 #[async_test]
2334 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2335 let client = logged_in_base_client(None).await;
2337 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2338
2339 let room_id = room_id!("!r:e.uk");
2341 let room = http::response::Room::new();
2342 let response = response_with_room(room_id, room);
2343 client
2344 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2345 .await
2346 .expect("Failed to process sync");
2347
2348 let _ = room_info_notable_update_stream.recv().await;
2350
2351 let room_id = room_id!("!r:e.uk");
2353 let events = vec![Raw::from_json_string(
2354 json!({
2355 "type": "m.room.member",
2356 "event_id": "$3",
2357 "content": { "membership": "join" },
2358 "sender": "@u:h.uk",
2359 "origin_server_ts": 12344445,
2360 "state_key": "@u:e.uk",
2361 })
2362 .to_string(),
2363 )
2364 .unwrap()];
2365 let room = assign!(http::response::Room::new(), {
2366 required_state: events,
2367 });
2368 let response = response_with_room(room_id, room);
2369 client
2370 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2371 .await
2372 .expect("Failed to process sync");
2373
2374 assert_matches!(
2376 room_info_notable_update_stream.recv().await,
2377 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2378 assert_eq!(received_room_id, room_id);
2379 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2380 }
2381 );
2382
2383 let events = vec![Raw::from_json_string(
2384 json!({
2385 "type": "m.room.member",
2386 "event_id": "$3",
2387 "content": { "membership": "leave" },
2388 "sender": "@u:h.uk",
2389 "origin_server_ts": 12344445,
2390 "state_key": "@u:e.uk",
2391 })
2392 .to_string(),
2393 )
2394 .unwrap()];
2395 let room = assign!(http::response::Room::new(), {
2396 required_state: events,
2397 });
2398 let response = response_with_room(room_id, room);
2399 client
2400 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2401 .await
2402 .expect("Failed to process sync");
2403
2404 let update = room_info_notable_update_stream.recv().await;
2406 assert_matches!(
2407 update,
2408 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2409 assert_eq!(received_room_id, room_id);
2410 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2411 }
2412 );
2413 }
2414
2415 #[async_test]
2416 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2417 let client = logged_in_base_client(None).await;
2419 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2420
2421 let room_id = room_id!("!r:e.uk");
2423 let room = http::response::Room::new();
2424 let response = response_with_room(room_id, room);
2425 client
2426 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2427 .await
2428 .expect("Failed to process sync");
2429
2430 assert_matches!(
2432 room_info_notable_update_stream.recv().await,
2433 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2434 assert_eq!(received_room_id, room_id);
2435 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2436 }
2437 );
2438
2439 let room_id = room_id!("!r:e.uk");
2442 let room_account_data_events = vec![Raw::from_json_string(
2443 json!({
2444 "type": "com.famedly.marked_unread",
2445 "event_id": "$1",
2446 "content": { "unread": true },
2447 "sender": client.session_meta().unwrap().user_id,
2448 "origin_server_ts": 12344445,
2449 })
2450 .to_string(),
2451 )
2452 .unwrap()];
2453 let mut response = response_with_room(room_id, http::response::Room::new());
2454 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2455
2456 client
2457 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2458 .await
2459 .expect("Failed to process sync");
2460
2461 assert_matches!(
2463 room_info_notable_update_stream.recv().await,
2464 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2465 assert_eq!(received_room_id, room_id);
2466 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2467 }
2468 );
2469
2470 client
2472 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2473 .await
2474 .expect("Failed to process sync");
2475
2476 assert_matches!(
2477 room_info_notable_update_stream.recv().await,
2478 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2479 assert_eq!(received_room_id, room_id);
2480 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2481 }
2482 );
2483
2484 let room_account_data_events = vec![Raw::from_json_string(
2486 json!({
2487 "type": "com.famedly.marked_unread",
2488 "event_id": "$1",
2489 "content": { "unread": false },
2490 "sender": client.session_meta().unwrap().user_id,
2491 "origin_server_ts": 12344445,
2492 })
2493 .to_string(),
2494 )
2495 .unwrap()];
2496 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2497 client
2498 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2499 .await
2500 .expect("Failed to process sync");
2501
2502 assert_matches!(
2503 room_info_notable_update_stream.recv().await,
2504 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2505 assert_eq!(received_room_id, room_id);
2506 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2507 }
2508 );
2509 }
2510
2511 #[async_test]
2512 async fn test_pinned_events_are_updated_on_sync() {
2513 let user_a_id = user_id!("@a:e.uk");
2514 let client = logged_in_base_client(Some(user_a_id)).await;
2515 let room_id = room_id!("!r:e.uk");
2516 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2517
2518 let mut room_response = http::response::Room::new();
2520 set_room_joined(&mut room_response, user_a_id);
2521 let response = response_with_room(room_id, room_response);
2522 client
2523 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2524 .await
2525 .expect("Failed to process sync");
2526
2527 let room = client.get_room(room_id).unwrap();
2529 let pinned_event_ids = room.pinned_event_ids();
2530 assert_matches!(pinned_event_ids, None);
2531
2532 let mut room_response = http::response::Room::new();
2534 room_response.required_state.push(make_state_event(
2535 user_a_id,
2536 "",
2537 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2538 None,
2539 ));
2540 let response = response_with_room(room_id, room_response);
2541 client
2542 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2543 .await
2544 .expect("Failed to process sync");
2545
2546 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2547 assert_eq!(pinned_event_ids.len(), 1);
2548 assert_eq!(pinned_event_ids[0], pinned_event_id);
2549
2550 let mut room_response = http::response::Room::new();
2552 room_response.required_state.push(make_state_event(
2553 user_a_id,
2554 "",
2555 RoomPinnedEventsEventContent::new(Vec::new()),
2556 None,
2557 ));
2558 let response = response_with_room(room_id, room_response);
2559 client
2560 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2561 .await
2562 .expect("Failed to process sync");
2563 let pinned_event_ids = room.pinned_event_ids().unwrap();
2564 assert!(pinned_event_ids.is_empty());
2565 }
2566
2567 #[async_test]
2568 async fn test_dms_are_processed_in_any_sync_response() {
2569 let current_user_id = user_id!("@current:e.uk");
2570 let client = logged_in_base_client(Some(current_user_id)).await;
2571 let user_a_id = user_id!("@a:e.uk");
2572 let user_b_id = user_id!("@b:e.uk");
2573 let room_id_1 = room_id!("!r:e.uk");
2574 let room_id_2 = room_id!("!s:e.uk");
2575
2576 let mut room_response = http::response::Room::new();
2577 set_room_joined(&mut room_response, user_a_id);
2578 let mut response = response_with_room(room_id_1, room_response);
2579 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2580 BTreeMap::new();
2581 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2582 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2583 response
2584 .extensions
2585 .account_data
2586 .global
2587 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2588 client
2589 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2590 .await
2591 .expect("Failed to process sync");
2592
2593 let room_1 = client.get_room(room_id_1).unwrap();
2594 assert!(room_1.is_direct().await.unwrap());
2595
2596 let mut room_response = http::response::Room::new();
2598 set_room_joined(&mut room_response, user_b_id);
2599 let response = response_with_room(room_id_2, room_response);
2600 client
2601 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2602 .await
2603 .expect("Failed to process sync");
2604
2605 let room_2 = client.get_room(room_id_2).unwrap();
2606 assert!(room_2.is_direct().await.unwrap());
2607 }
2608
2609 #[async_test]
2610 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2611 let user_id = user_id!("@raclette:patate");
2612 let client = logged_in_base_client(Some(user_id)).await;
2613 let room_id_0 = room_id!("!r0");
2614 let room_id_1 = room_id!("!r1");
2615 let room_id_2 = room_id!("!r2");
2616
2617 let requested_required_states = RequestedRequiredStates::from(&{
2634 let mut request = http::Request::new();
2635
2636 request.room_subscriptions.insert(room_id_0.to_owned(), {
2637 let mut room_subscription = http::request::RoomSubscription::default();
2638
2639 room_subscription
2640 .required_state
2641 .push((StateEventType::RoomEncryption, "".to_owned()));
2642
2643 room_subscription
2644 });
2645
2646 request
2647 });
2648
2649 let mut response = http::Response::new("0".to_owned());
2650
2651 {
2655 let not_encrypted_room = http::response::Room::new();
2656 let mut encrypted_room = http::response::Room::new();
2657 set_room_is_encrypted(&mut encrypted_room, user_id);
2658
2659 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2660 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2661 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2662 }
2663
2664 client
2665 .process_sliding_sync(&response, &(), &requested_required_states)
2666 .await
2667 .expect("Failed to process sync");
2668
2669 assert_matches!(
2671 client.get_room(room_id_0).unwrap().encryption_state(),
2672 EncryptionState::Encrypted
2673 );
2674 assert_matches!(
2675 client.get_room(room_id_1).unwrap().encryption_state(),
2676 EncryptionState::Encrypted
2677 );
2678 assert_matches!(
2680 client.get_room(room_id_2).unwrap().encryption_state(),
2681 EncryptionState::NotEncrypted
2682 )
2683 }
2684
2685 #[async_test]
2686 async fn test_room_encryption_state_is_unknown() {
2687 let user_id = user_id!("@raclette:patate");
2688 let client = logged_in_base_client(Some(user_id)).await;
2689 let room_id_0 = room_id!("!r0");
2690 let room_id_1 = room_id!("!r1");
2691
2692 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2705
2706 let mut response = http::Response::new("0".to_owned());
2707
2708 {
2710 let not_encrypted_room = http::response::Room::new();
2711 let mut encrypted_room = http::response::Room::new();
2712 set_room_is_encrypted(&mut encrypted_room, user_id);
2713
2714 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2715 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2716 }
2717
2718 client
2719 .process_sliding_sync(&response, &(), &requested_required_states)
2720 .await
2721 .expect("Failed to process sync");
2722
2723 assert_matches!(
2726 client.get_room(room_id_0).unwrap().encryption_state(),
2727 EncryptionState::Encrypted
2728 );
2729 assert_matches!(
2732 client.get_room(room_id_1).unwrap().encryption_state(),
2733 EncryptionState::Unknown
2734 );
2735 }
2736
2737 #[cfg(feature = "e2e-encryption")]
2738 async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2739 let room = make_room();
2740 let mut room_info = room.clone_info();
2741 cache_latest_events(&room, &mut room_info, events, None, None).await;
2742 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2743 room.latest_event().map(|latest_event| latest_event.event().clone())
2744 }
2745
2746 #[cfg(feature = "e2e-encryption")]
2747 fn rawev_id(event: TimelineEvent) -> String {
2748 event.event_id().unwrap().to_string()
2749 }
2750
2751 fn ev_id(event: Option<TimelineEvent>) -> String {
2752 event.unwrap().event_id().unwrap().to_string()
2753 }
2754
2755 #[cfg(feature = "e2e-encryption")]
2756 fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2757 events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2758 }
2759
2760 #[cfg(feature = "e2e-encryption")]
2761 fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2762 events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2763 }
2764
2765 #[cfg(feature = "e2e-encryption")]
2766 fn make_room() -> Room {
2767 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2768
2769 Room::new(
2770 user_id!("@u:e.co"),
2771 Arc::new(MemoryStore::new()),
2772 room_id!("!r:e.co"),
2773 RoomState::Joined,
2774 sender,
2775 )
2776 }
2777
2778 fn make_raw_event(event_type: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2779 Raw::from_json_string(
2780 json!({
2781 "type": event_type,
2782 "event_id": id,
2783 "content": { "msgtype": "m.text", "body": "my msg" },
2784 "sender": "@u:h.uk",
2785 "origin_server_ts": 12344445,
2786 })
2787 .to_string(),
2788 )
2789 .unwrap()
2790 }
2791
2792 #[cfg(feature = "e2e-encryption")]
2793 fn make_event(event_type: &str, id: &str) -> TimelineEvent {
2794 TimelineEvent::new(make_raw_event(event_type, id))
2795 }
2796
2797 #[cfg(feature = "e2e-encryption")]
2798 fn make_encrypted_event(id: &str) -> TimelineEvent {
2799 TimelineEvent::new_utd_event(
2800 Raw::from_json_string(
2801 json!({
2802 "type": "m.room.encrypted",
2803 "event_id": id,
2804 "content": {
2805 "algorithm": "m.megolm.v1.aes-sha2",
2806 "ciphertext": "",
2807 "sender_key": "",
2808 "device_id": "",
2809 "session_id": "",
2810 },
2811 "sender": "@u:h.uk",
2812 "origin_server_ts": 12344445,
2813 })
2814 .to_string(),
2815 )
2816 .unwrap(),
2817 UnableToDecryptInfo {
2818 session_id: Some("".to_owned()),
2819 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2820 },
2821 )
2822 }
2823
2824 async fn membership(
2825 client: &BaseClient,
2826 room_id: &RoomId,
2827 user_id: &UserId,
2828 ) -> MembershipState {
2829 let room = client.get_room(room_id).expect("Room not found!");
2830 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2831 member.membership().clone()
2832 }
2833
2834 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2835 let room = client.get_room(room_id).expect("Room not found!");
2836 room.direct_targets()
2837 }
2838
2839 async fn create_dm(
2842 client: &BaseClient,
2843 room_id: &RoomId,
2844 my_id: &UserId,
2845 their_id: &UserId,
2846 other_state: MembershipState,
2847 ) {
2848 let mut room = http::response::Room::new();
2849 set_room_joined(&mut room, my_id);
2850
2851 match other_state {
2852 MembershipState::Join => {
2853 room.joined_count = Some(uint!(2));
2854 room.invited_count = None;
2855 }
2856
2857 MembershipState::Invite => {
2858 room.joined_count = Some(uint!(1));
2859 room.invited_count = Some(uint!(1));
2860 }
2861
2862 _ => {
2863 room.joined_count = Some(uint!(1));
2864 room.invited_count = None;
2865 }
2866 }
2867
2868 room.required_state.push(make_membership_event(their_id, other_state));
2869
2870 let mut response = response_with_room(room_id, room);
2871 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2872 client
2873 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2874 .await
2875 .expect("Failed to process sync");
2876 }
2877
2878 async fn update_room_membership(
2880 client: &BaseClient,
2881 room_id: &RoomId,
2882 user_id: &UserId,
2883 new_state: MembershipState,
2884 ) {
2885 let mut room = http::response::Room::new();
2886 room.required_state.push(make_membership_event(user_id, new_state));
2887 let response = response_with_room(room_id, room);
2888 client
2889 .process_sliding_sync(&response, &(), &RequestedRequiredStates::default())
2890 .await
2891 .expect("Failed to process sync");
2892 }
2893
2894 fn set_direct_with(
2895 response: &mut http::Response,
2896 user_id: OwnedUserId,
2897 room_ids: Vec<OwnedRoomId>,
2898 ) {
2899 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2900 BTreeMap::new();
2901 direct_content.insert(user_id.into(), room_ids);
2902 response
2903 .extensions
2904 .account_data
2905 .global
2906 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2907 }
2908
2909 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2910 let mut response = http::Response::new("5".to_owned());
2911 response.rooms.insert(room_id.to_owned(), room);
2912 response
2913 }
2914
2915 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2916 let mut room = http::response::Room::new();
2917
2918 let mut avatar_event_content = RoomAvatarEventContent::new();
2919 avatar_event_content.url = Some(avatar_uri.to_owned());
2920
2921 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2922
2923 room
2924 }
2925
2926 fn room_with_canonical_alias(
2927 room_alias_id: &RoomAliasId,
2928 user_id: &UserId,
2929 ) -> http::response::Room {
2930 let mut room = http::response::Room::new();
2931
2932 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2933 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2934
2935 room.required_state.push(make_state_event(
2936 user_id,
2937 "",
2938 canonical_alias_event_content,
2939 None,
2940 ));
2941
2942 room
2943 }
2944
2945 fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2946 let mut room = http::response::Room::new();
2947 room.timeline.extend(
2948 events
2949 .iter()
2950 .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2951 .collect::<Vec<_>>(),
2952 );
2953 room
2954 }
2955
2956 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2957 room.required_state.push(make_state_event(
2958 sender,
2959 "",
2960 RoomNameEventContent::new(name),
2961 None,
2962 ));
2963 }
2964
2965 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2966 let evt = Raw::new(&json!({
2970 "type": "m.room.member",
2971 "sender": inviter,
2972 "content": {
2973 "is_direct": true,
2974 "membership": "invite",
2975 },
2976 "state_key": invitee,
2977 }))
2978 .expect("Failed to make raw event")
2979 .cast();
2980
2981 room.invite_state = Some(vec![evt]);
2982
2983 room.required_state.push(make_state_event(
2986 inviter,
2987 invitee.as_str(),
2988 RoomMemberEventContent::new(MembershipState::Invite),
2989 None,
2990 ));
2991 }
2992
2993 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2994 let evt = Raw::new(&json!({
2998 "type": "m.room.member",
2999 "sender": knocker,
3000 "content": {
3001 "is_direct": true,
3002 "membership": "knock",
3003 },
3004 "state_key": knocker,
3005 }))
3006 .expect("Failed to make raw event")
3007 .cast();
3008
3009 room.invite_state = Some(vec![evt]);
3010 }
3011
3012 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
3013 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
3014 }
3015
3016 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
3017 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
3018 }
3019
3020 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
3021 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
3022 }
3023
3024 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
3025 room.required_state.push(make_encryption_event(user_id));
3026 }
3027
3028 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
3029 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
3030 }
3031
3032 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
3033 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
3034 }
3035
3036 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
3037 Raw::new(&json!({
3038 "type": content.event_type(),
3039 "content": content,
3040 }))
3041 .expect("Failed to create account data event")
3042 .cast()
3043 }
3044
3045 fn make_state_event<C: StateEventContent, E>(
3046 sender: &UserId,
3047 state_key: &str,
3048 content: C,
3049 prev_content: Option<C>,
3050 ) -> Raw<E> {
3051 let unsigned = if let Some(prev_content) = prev_content {
3052 json!({ "prev_content": prev_content })
3053 } else {
3054 json!({})
3055 };
3056
3057 Raw::new(&json!({
3058 "type": content.event_type(),
3059 "state_key": state_key,
3060 "content": content,
3061 "event_id": event_id!("$evt"),
3062 "sender": sender,
3063 "origin_server_ts": 10,
3064 "unsigned": unsigned,
3065 }))
3066 .expect("Failed to create state event")
3067 .cast()
3068 }
3069}