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