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 room.update_room_info_with_store_guard(state_store_guard, |room_info| {
238 (room_info, crate::RoomInfoNotableUpdateReasons::HEROES)
239 })
240 .map_err(crate::StoreError::from)?;
241 }
242 }
243 }
244
245 let mut context = processors::Context::default();
246
247 processors::room::display_name::update_for_rooms(
250 &mut context,
251 &room_updates,
252 &self.state_store,
253 )
254 .await;
255
256 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
258
259 Ok(SyncResponse {
260 rooms: room_updates,
261 notifications,
262 presence: Default::default(),
263 account_data: extensions.account_data.global.clone(),
264 to_device: Default::default(),
265 })
266 }
267
268 #[doc(hidden)]
271 pub async fn process_sliding_sync_receipts_extension_for_room(
272 &self,
273 room_id: &OwnedRoomId,
274 response: &http::Response,
275 state_store_guard: &MutexGuard<'_, ()>,
276 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
277 let mut context = processors::Context::default();
278
279 let mut save_context = false;
280
281 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
283 response.extensions.receipts.rooms.get(room_id)
284 {
285 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
286 &mut context,
287 room_id,
288 receipt_ephemeral_event,
289 );
290 save_context = true;
291 Some(receipt_ephemeral_event.clone())
292 } else {
293 None
294 };
295
296 if save_context {
298 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
299 }
300
301 Ok(receipt_ephemeral_event)
302 }
303}
304
305#[cfg(all(test, not(target_family = "wasm")))]
306mod tests {
307 use std::collections::{BTreeMap, HashSet};
308
309 use assert_matches::assert_matches;
310 use matrix_sdk_test::async_test;
311 use ruma::{
312 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
313 api::client::sync::sync_events::UnreadNotificationsCount,
314 assign, event_id,
315 events::{
316 GlobalAccountDataEventContent, StateEventContent, StateEventType,
317 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
318 room::{
319 avatar::RoomAvatarEventContent,
320 canonical_alias::RoomCanonicalAliasEventContent,
321 encryption::RoomEncryptionEventContent,
322 member::{MembershipState, RoomMemberEventContent},
323 name::RoomNameEventContent,
324 pinned_events::RoomPinnedEventsEventContent,
325 },
326 },
327 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
328 profile::{ProfileFieldName, UserProfileChanges, UserProfileUpdate},
329 room_alias_id, room_id,
330 serde::Raw,
331 uint, user_id,
332 };
333 use serde_json::json;
334 #[cfg(feature = "unstable-msc4426")]
335 use stream_assert::{assert_pending, assert_ready};
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 room = client.get_room(room_id).expect("The room should be known");
1686 let mut room_info_subscriber = room.subscribe_info();
1687 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1688
1689 assert_pending!(room_info_subscriber);
1690
1691 let mut response = http::Response::new("1".to_owned());
1693 response.extensions.profiles.users.insert(
1694 alice.clone(),
1695 make_profile_update(ProfileFieldName::Status, json!({ "text": "Away", "emoji": "🌴" })),
1696 );
1697 client
1698 .process_sliding_sync(
1699 &response,
1700 &RequestedRequiredStates::default(),
1701 &client.state_store_lock().lock().await,
1702 )
1703 .await
1704 .expect("Failed to process sync");
1705
1706 assert_matches!(
1709 room_info_notable_update.recv().await,
1710 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1711 assert_eq!(received_room_id, room_id);
1712 assert!(reasons.contains(RoomInfoNotableUpdateReasons::HEROES));
1713 }
1714 );
1715 assert!(room_info_notable_update.is_empty());
1716
1717 assert_ready!(room_info_subscriber);
1720 assert_pending!(room_info_subscriber);
1721
1722 let heroes = room.heroes().await;
1724 assert_eq!(heroes.len(), 1);
1725 assert_eq!(heroes[0].user_id, alice);
1726 let status = heroes[0].status.as_ref().expect("Alice's status should be set");
1727 assert_eq!(status.text, "Away");
1728 assert_eq!(status.emoji, "🌴");
1729 }
1730
1731 #[async_test]
1732 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1733 let client = logged_in_base_client(None).await;
1735 let room_id = room_id!("!r:e.uk");
1736
1737 let room = assign!(http::response::Room::new(), {
1739 bump_stamp: Some(42u32.into()),
1740 });
1741 let response = response_with_room(room_id, room);
1742 client
1743 .process_sliding_sync(
1744 &response,
1745 &RequestedRequiredStates::default(),
1746 &client.state_store_lock().lock().await,
1747 )
1748 .await
1749 .expect("Failed to process sync");
1750
1751 let client_room = client.get_room(room_id).expect("No room found");
1753 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1754 }
1755
1756 #[async_test]
1757 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1758 let client = logged_in_base_client(None).await;
1760 let room_id = room_id!("!r:e.uk");
1761
1762 {
1763 let room = assign!(http::response::Room::new(), {
1765 bump_stamp: Some(42u32.into()),
1766 });
1767 let response = response_with_room(room_id, room);
1768 client
1769 .process_sliding_sync(
1770 &response,
1771 &RequestedRequiredStates::default(),
1772 &client.state_store_lock().lock().await,
1773 )
1774 .await
1775 .expect("Failed to process sync");
1776
1777 let client_room = client.get_room(room_id).expect("No room found");
1779 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1780 }
1781
1782 {
1783 let room = assign!(http::response::Room::new(), {
1785 bump_stamp: None,
1786 });
1787 let response = response_with_room(room_id, room);
1788 client
1789 .process_sliding_sync(
1790 &response,
1791 &RequestedRequiredStates::default(),
1792 &client.state_store_lock().lock().await,
1793 )
1794 .await
1795 .expect("Failed to process sync");
1796
1797 let client_room = client.get_room(room_id).expect("No room found");
1799 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1800 }
1801
1802 {
1803 let room = assign!(http::response::Room::new(), {
1806 bump_stamp: Some(153u32.into()),
1807 });
1808 let response = response_with_room(room_id, room);
1809 client
1810 .process_sliding_sync(
1811 &response,
1812 &RequestedRequiredStates::default(),
1813 &client.state_store_lock().lock().await,
1814 )
1815 .await
1816 .expect("Failed to process sync");
1817
1818 let client_room = client.get_room(room_id).expect("No room found");
1820 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1821 }
1822 }
1823
1824 #[async_test]
1825 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1826 let client = logged_in_base_client(None).await;
1828 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1829 let room_id = room_id!("!r:e.uk");
1830
1831 let room = assign!(http::response::Room::new(), {
1833 bump_stamp: Some(42u32.into()),
1834 });
1835 let response = response_with_room(room_id, room);
1836 client
1837 .process_sliding_sync(
1838 &response,
1839 &RequestedRequiredStates::default(),
1840 &client.state_store_lock().lock().await,
1841 )
1842 .await
1843 .expect("Failed to process sync");
1844
1845 assert_matches!(
1848 room_info_notable_update_stream.recv().await,
1849 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1850 assert_eq!(received_room_id, room_id);
1851 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1852 }
1853 );
1854 assert_matches!(
1855 room_info_notable_update_stream.recv().await,
1856 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1857 assert_eq!(received_room_id, room_id);
1858 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1859 }
1860 );
1861 assert!(room_info_notable_update_stream.is_empty());
1862
1863 let room = assign!(http::response::Room::new(), {
1865 bump_stamp: Some(43u32.into()),
1866 });
1867 let response = response_with_room(room_id, room);
1868 client
1869 .process_sliding_sync(
1870 &response,
1871 &RequestedRequiredStates::default(),
1872 &client.state_store_lock().lock().await,
1873 )
1874 .await
1875 .expect("Failed to process sync");
1876
1877 assert_matches!(
1879 room_info_notable_update_stream.recv().await,
1880 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1881 assert_eq!(received_room_id, room_id);
1882 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1883 }
1884 );
1885 assert!(room_info_notable_update_stream.is_empty());
1886 }
1887
1888 #[async_test]
1889 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1890 let client = logged_in_base_client(None).await;
1892 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1893
1894 let room_id = room_id!("!r:e.uk");
1896 let room = http::response::Room::new();
1897 let response = response_with_room(room_id, room);
1898 client
1899 .process_sliding_sync(
1900 &response,
1901 &RequestedRequiredStates::default(),
1902 &client.state_store_lock().lock().await,
1903 )
1904 .await
1905 .expect("Failed to process sync");
1906
1907 assert_matches!(
1909 room_info_notable_update_stream.recv().await,
1910 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1911 assert_eq!(received_room_id, room_id);
1912 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1913 }
1914 );
1915 assert_matches!(
1916 room_info_notable_update_stream.recv().await,
1917 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1918 assert_eq!(received_room_id, room_id);
1919 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1920 }
1921 );
1922
1923 let room_id = room_id!("!r:e.uk");
1925 let events = vec![
1926 Raw::from_json_string(
1927 json!({
1928 "type": "m.room.member",
1929 "event_id": "$3",
1930 "content": { "membership": "join" },
1931 "sender": "@u:h.uk",
1932 "origin_server_ts": 12344445,
1933 "state_key": "@u:e.uk",
1934 })
1935 .to_string(),
1936 )
1937 .unwrap(),
1938 ];
1939 let room = assign!(http::response::Room::new(), {
1940 required_state: events,
1941 });
1942 let response = response_with_room(room_id, room);
1943 client
1944 .process_sliding_sync(
1945 &response,
1946 &RequestedRequiredStates::default(),
1947 &client.state_store_lock().lock().await,
1948 )
1949 .await
1950 .expect("Failed to process sync");
1951
1952 assert_matches!(
1954 room_info_notable_update_stream.recv().await,
1955 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1956 assert_eq!(received_room_id, room_id);
1957 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1958 }
1959 );
1960 assert!(room_info_notable_update_stream.is_empty());
1961
1962 let events = vec![
1963 Raw::from_json_string(
1964 json!({
1965 "type": "m.room.member",
1966 "event_id": "$3",
1967 "content": { "membership": "leave" },
1968 "sender": "@u:h.uk",
1969 "origin_server_ts": 12344445,
1970 "state_key": "@u:e.uk",
1971 })
1972 .to_string(),
1973 )
1974 .unwrap(),
1975 ];
1976 let room = assign!(http::response::Room::new(), {
1977 required_state: events,
1978 });
1979 let response = response_with_room(room_id, room);
1980 client
1981 .process_sliding_sync(
1982 &response,
1983 &RequestedRequiredStates::default(),
1984 &client.state_store_lock().lock().await,
1985 )
1986 .await
1987 .expect("Failed to process sync");
1988
1989 assert_matches!(
1991 room_info_notable_update_stream.recv().await,
1992 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1993 assert_eq!(received_room_id, room_id);
1994 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1995 }
1996 );
1997 assert!(room_info_notable_update_stream.is_empty());
1998 }
1999
2000 #[async_test]
2001 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2002 let client = logged_in_base_client(None).await;
2004 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2005
2006 let room_id = room_id!("!r:e.uk");
2008 let room = http::response::Room::new();
2009 let response = response_with_room(room_id, room);
2010 client
2011 .process_sliding_sync(
2012 &response,
2013 &RequestedRequiredStates::default(),
2014 &client.state_store_lock().lock().await,
2015 )
2016 .await
2017 .expect("Failed to process sync");
2018
2019 assert_matches!(
2021 room_info_notable_update_stream.recv().await,
2022 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2023 assert_eq!(received_room_id, room_id);
2024 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2025 }
2026 );
2027 assert_matches!(
2028 room_info_notable_update_stream.recv().await,
2029 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2030 assert_eq!(received_room_id, room_id);
2031 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2032 }
2033 );
2034 assert!(room_info_notable_update_stream.is_empty());
2035
2036 let room_id = room_id!("!r:e.uk");
2039 let room_account_data_events = vec![
2040 Raw::from_json_string(
2041 json!({
2042 "type": "m.marked_unread",
2043 "event_id": "$1",
2044 "content": { "unread": true },
2045 "sender": client.session_meta().unwrap().user_id,
2046 "origin_server_ts": 12344445,
2047 })
2048 .to_string(),
2049 )
2050 .unwrap(),
2051 ];
2052 let mut response = response_with_room(room_id, http::response::Room::new());
2053 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2054
2055 client
2056 .process_sliding_sync(
2057 &response,
2058 &RequestedRequiredStates::default(),
2059 &client.state_store_lock().lock().await,
2060 )
2061 .await
2062 .expect("Failed to process sync");
2063
2064 assert_matches!(
2066 room_info_notable_update_stream.recv().await,
2067 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2068 assert_eq!(received_room_id, room_id);
2069 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2070 }
2071 );
2072
2073 client
2075 .process_sliding_sync(
2076 &response,
2077 &RequestedRequiredStates::default(),
2078 &client.state_store_lock().lock().await,
2079 )
2080 .await
2081 .expect("Failed to process sync");
2082
2083 assert_matches!(
2084 room_info_notable_update_stream.recv().await,
2085 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2086 assert_eq!(received_room_id, room_id);
2087 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2088 }
2089 );
2090 assert!(room_info_notable_update_stream.is_empty());
2091
2092 let room_account_data_events = vec![
2094 Raw::from_json_string(
2095 json!({
2096 "type": "m.marked_unread",
2097 "event_id": "$1",
2098 "content": { "unread": false },
2099 "sender": client.session_meta().unwrap().user_id,
2100 "origin_server_ts": 12344445,
2101 })
2102 .to_string(),
2103 )
2104 .unwrap(),
2105 ];
2106 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2107 client
2108 .process_sliding_sync(
2109 &response,
2110 &RequestedRequiredStates::default(),
2111 &client.state_store_lock().lock().await,
2112 )
2113 .await
2114 .expect("Failed to process sync");
2115
2116 assert_matches!(
2117 room_info_notable_update_stream.recv().await,
2118 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2119 assert_eq!(received_room_id, room_id);
2120 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2121 }
2122 );
2123 assert!(room_info_notable_update_stream.is_empty());
2124 }
2125
2126 #[async_test]
2127 async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
2128 let client = logged_in_base_client(None).await;
2130 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2131
2132 let room_id = room_id!("!r:e.uk");
2134 let room = http::response::Room::new();
2135 let response = response_with_room(room_id, room);
2136 client
2137 .process_sliding_sync(
2138 &response,
2139 &RequestedRequiredStates::default(),
2140 &client.state_store_lock().lock().await,
2141 )
2142 .await
2143 .expect("Failed to process sync");
2144
2145 assert_matches!(
2147 room_info_notable_update_stream.recv().await,
2148 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2149 assert_eq!(received_room_id, room_id);
2150 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2151 }
2152 );
2153 assert_matches!(
2154 room_info_notable_update_stream.recv().await,
2155 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2156 assert_eq!(received_room_id, room_id);
2157 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2158 }
2159 );
2160 assert!(room_info_notable_update_stream.is_empty());
2161
2162 let room_account_data_events = vec![
2165 Raw::from_json_string(
2166 json!({
2167 "type": "m.fully_read",
2168 "content": { "event_id": "$first" },
2169 })
2170 .to_string(),
2171 )
2172 .unwrap(),
2173 ];
2174 let mut response = response_with_room(room_id, http::response::Room::new());
2175 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2176
2177 client
2178 .process_sliding_sync(
2179 &response,
2180 &RequestedRequiredStates::default(),
2181 &client.state_store_lock().lock().await,
2182 )
2183 .await
2184 .expect("Failed to process sync");
2185
2186 assert_matches!(
2188 room_info_notable_update_stream.recv().await,
2189 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2190 assert_eq!(received_room_id, room_id);
2191 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2192 }
2193 );
2194
2195 let room = client.get_room(room_id).expect("room should exist");
2196 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2197
2198 client
2200 .process_sliding_sync(
2201 &response,
2202 &RequestedRequiredStates::default(),
2203 &client.state_store_lock().lock().await,
2204 )
2205 .await
2206 .expect("Failed to process sync");
2207
2208 assert_matches!(
2209 room_info_notable_update_stream.recv().await,
2210 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2211 assert_eq!(received_room_id, room_id);
2212 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2213 }
2214 );
2215 assert!(room_info_notable_update_stream.is_empty());
2216
2217 let room_account_data_events = vec![
2219 Raw::from_json_string(
2220 json!({
2221 "type": "m.fully_read",
2222 "content": { "event_id": "$second" },
2223 })
2224 .to_string(),
2225 )
2226 .unwrap(),
2227 ];
2228 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2229 client
2230 .process_sliding_sync(
2231 &response,
2232 &RequestedRequiredStates::default(),
2233 &client.state_store_lock().lock().await,
2234 )
2235 .await
2236 .expect("Failed to process sync");
2237
2238 assert_matches!(
2239 room_info_notable_update_stream.recv().await,
2240 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2241 assert_eq!(received_room_id, room_id);
2242 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2243 }
2244 );
2245 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2246 assert!(room_info_notable_update_stream.is_empty());
2247 }
2248
2249 #[async_test]
2250 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2251 let client = logged_in_base_client(None).await;
2253 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2254
2255 let room_id = room_id!("!r:e.uk");
2257 let room = http::response::Room::new();
2258 let response = response_with_room(room_id, room);
2259 client
2260 .process_sliding_sync(
2261 &response,
2262 &RequestedRequiredStates::default(),
2263 &client.state_store_lock().lock().await,
2264 )
2265 .await
2266 .expect("Failed to process sync");
2267
2268 assert_matches!(
2270 room_info_notable_update_stream.recv().await,
2271 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2272 assert_eq!(received_room_id, room_id);
2273 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2274 }
2275 );
2276 assert_matches!(
2277 room_info_notable_update_stream.recv().await,
2278 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2279 assert_eq!(received_room_id, room_id);
2280 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2281 }
2282 );
2283 assert!(room_info_notable_update_stream.is_empty());
2284
2285 let room_id = room_id!("!r:e.uk");
2288 let unstable_room_account_data_events = vec![
2289 Raw::from_json_string(
2290 json!({
2291 "type": "com.famedly.marked_unread",
2292 "event_id": "$1",
2293 "content": { "unread": true },
2294 "sender": client.session_meta().unwrap().user_id,
2295 "origin_server_ts": 12344445,
2296 })
2297 .to_string(),
2298 )
2299 .unwrap(),
2300 ];
2301 let mut response = response_with_room(room_id, http::response::Room::new());
2302 response
2303 .extensions
2304 .account_data
2305 .rooms
2306 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2307
2308 client
2309 .process_sliding_sync(
2310 &response,
2311 &RequestedRequiredStates::default(),
2312 &client.state_store_lock().lock().await,
2313 )
2314 .await
2315 .expect("Failed to process sync");
2316
2317 assert_matches!(
2319 room_info_notable_update_stream.recv().await,
2320 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2321 assert_eq!(received_room_id, room_id);
2322 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2323 }
2324 );
2325 assert!(room_info_notable_update_stream.is_empty());
2326
2327 let stable_room_account_data_events = vec![
2329 Raw::from_json_string(
2330 json!({
2331 "type": "m.marked_unread",
2332 "event_id": "$1",
2333 "content": { "unread": false },
2334 "sender": client.session_meta().unwrap().user_id,
2335 "origin_server_ts": 12344445,
2336 })
2337 .to_string(),
2338 )
2339 .unwrap(),
2340 ];
2341 response
2342 .extensions
2343 .account_data
2344 .rooms
2345 .insert(room_id.to_owned(), stable_room_account_data_events);
2346 client
2347 .process_sliding_sync(
2348 &response,
2349 &RequestedRequiredStates::default(),
2350 &client.state_store_lock().lock().await,
2351 )
2352 .await
2353 .expect("Failed to process sync");
2354
2355 assert_matches!(
2357 room_info_notable_update_stream.recv().await,
2358 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2359 assert_eq!(received_room_id, room_id);
2360 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2361 }
2362 );
2363 assert!(room_info_notable_update_stream.is_empty());
2364
2365 response
2368 .extensions
2369 .account_data
2370 .rooms
2371 .insert(room_id.to_owned(), unstable_room_account_data_events);
2372 client
2373 .process_sliding_sync(
2374 &response,
2375 &RequestedRequiredStates::default(),
2376 &client.state_store_lock().lock().await,
2377 )
2378 .await
2379 .expect("Failed to process sync");
2380
2381 assert_matches!(
2383 room_info_notable_update_stream.recv().await,
2384 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2385 assert_eq!(received_room_id, room_id);
2386 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2387 }
2388 );
2389 assert!(room_info_notable_update_stream.is_empty());
2390
2391 let stable_room_account_data_events = vec![
2394 Raw::from_json_string(
2395 json!({
2396 "type": "m.marked_unread",
2397 "event_id": "$3",
2398 "content": { "unread": true },
2399 "sender": client.session_meta().unwrap().user_id,
2400 "origin_server_ts": 12344445,
2401 })
2402 .to_string(),
2403 )
2404 .unwrap(),
2405 ];
2406 response
2407 .extensions
2408 .account_data
2409 .rooms
2410 .insert(room_id.to_owned(), stable_room_account_data_events);
2411 client
2412 .process_sliding_sync(
2413 &response,
2414 &RequestedRequiredStates::default(),
2415 &client.state_store_lock().lock().await,
2416 )
2417 .await
2418 .expect("Failed to process sync");
2419
2420 assert_matches!(
2422 room_info_notable_update_stream.recv().await,
2423 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2424 assert_eq!(received_room_id, room_id);
2425 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2426 }
2427 );
2428 assert!(room_info_notable_update_stream.is_empty());
2429 }
2430
2431 #[async_test]
2432 async fn test_pinned_events_are_updated_on_sync() {
2433 let user_a_id = user_id!("@a:e.uk");
2434 let client = logged_in_base_client(Some(user_a_id)).await;
2435 let room_id = room_id!("!r:e.uk");
2436 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2437
2438 let mut room_response = http::response::Room::new();
2440 set_room_joined(&mut room_response, user_a_id);
2441 let response = response_with_room(room_id, room_response);
2442 client
2443 .process_sliding_sync(
2444 &response,
2445 &RequestedRequiredStates::default(),
2446 &client.state_store_lock().lock().await,
2447 )
2448 .await
2449 .expect("Failed to process sync");
2450
2451 let room = client.get_room(room_id).unwrap();
2453 let pinned_event_ids = room.pinned_event_ids();
2454 assert_matches!(pinned_event_ids, None);
2455
2456 let mut room_response = http::response::Room::new();
2458 room_response.required_state.push(make_state_event(
2459 user_a_id,
2460 "",
2461 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2462 None,
2463 ));
2464 let response = response_with_room(room_id, room_response);
2465 client
2466 .process_sliding_sync(
2467 &response,
2468 &RequestedRequiredStates::default(),
2469 &client.state_store_lock().lock().await,
2470 )
2471 .await
2472 .expect("Failed to process sync");
2473
2474 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2475 assert_eq!(pinned_event_ids.len(), 1);
2476 assert_eq!(pinned_event_ids[0], pinned_event_id);
2477
2478 let mut room_response = http::response::Room::new();
2480 room_response.required_state.push(make_state_event(
2481 user_a_id,
2482 "",
2483 RoomPinnedEventsEventContent::new(Vec::new()),
2484 None,
2485 ));
2486 let response = response_with_room(room_id, room_response);
2487 client
2488 .process_sliding_sync(
2489 &response,
2490 &RequestedRequiredStates::default(),
2491 &client.state_store_lock().lock().await,
2492 )
2493 .await
2494 .expect("Failed to process sync");
2495 let pinned_event_ids = room.pinned_event_ids().unwrap();
2496 assert!(pinned_event_ids.is_empty());
2497 }
2498
2499 #[async_test]
2500 async fn test_dms_are_processed_in_any_sync_response() {
2501 let current_user_id = user_id!("@current:e.uk");
2502 let client = logged_in_base_client(Some(current_user_id)).await;
2503 let user_a_id = user_id!("@a:e.uk");
2504 let user_b_id = user_id!("@b:e.uk");
2505 let room_id_1 = room_id!("!r:e.uk");
2506 let room_id_2 = room_id!("!s:e.uk");
2507
2508 let mut room_response = http::response::Room::new();
2509 set_room_joined(&mut room_response, user_a_id);
2510 let mut response = response_with_room(room_id_1, room_response);
2511 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2512 BTreeMap::new();
2513 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2514 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2515 response
2516 .extensions
2517 .account_data
2518 .global
2519 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2520 client
2521 .process_sliding_sync(
2522 &response,
2523 &RequestedRequiredStates::default(),
2524 &client.state_store_lock().lock().await,
2525 )
2526 .await
2527 .expect("Failed to process sync");
2528
2529 let room_1 = client.get_room(room_id_1).unwrap();
2530 assert!(room_1.is_direct().await.unwrap());
2531
2532 let mut room_response = http::response::Room::new();
2534 set_room_joined(&mut room_response, user_b_id);
2535 let response = response_with_room(room_id_2, room_response);
2536 client
2537 .process_sliding_sync(
2538 &response,
2539 &RequestedRequiredStates::default(),
2540 &client.state_store_lock().lock().await,
2541 )
2542 .await
2543 .expect("Failed to process sync");
2544
2545 let room_2 = client.get_room(room_id_2).unwrap();
2546 assert!(room_2.is_direct().await.unwrap());
2547 }
2548
2549 #[async_test]
2550 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2551 let user_id = user_id!("@raclette:patate");
2552 let client = logged_in_base_client(Some(user_id)).await;
2553 let room_id_0 = room_id!("!r0");
2554 let room_id_1 = room_id!("!r1");
2555 let room_id_2 = room_id!("!r2");
2556
2557 let requested_required_states = RequestedRequiredStates::from(&{
2574 let mut request = http::Request::new();
2575
2576 request.room_subscriptions.insert(room_id_0.to_owned(), {
2577 let mut room_subscription = http::request::RoomSubscription::default();
2578
2579 room_subscription
2580 .required_state
2581 .push((StateEventType::RoomEncryption, "".to_owned()));
2582
2583 room_subscription
2584 });
2585
2586 request
2587 });
2588
2589 let mut response = http::Response::new("0".to_owned());
2590
2591 {
2595 let not_encrypted_room = http::response::Room::new();
2596 let mut encrypted_room = http::response::Room::new();
2597 set_room_is_encrypted(&mut encrypted_room, user_id);
2598
2599 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2600 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2601 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2602 }
2603
2604 client
2605 .process_sliding_sync(
2606 &response,
2607 &requested_required_states,
2608 &client.state_store_lock().lock().await,
2609 )
2610 .await
2611 .expect("Failed to process sync");
2612
2613 assert_matches!(
2615 client.get_room(room_id_0).unwrap().encryption_state(),
2616 EncryptionState::Encrypted
2617 );
2618 assert_matches!(
2619 client.get_room(room_id_1).unwrap().encryption_state(),
2620 EncryptionState::Encrypted
2621 );
2622 assert_matches!(
2624 client.get_room(room_id_2).unwrap().encryption_state(),
2625 EncryptionState::NotEncrypted
2626 )
2627 }
2628
2629 #[async_test]
2630 async fn test_room_encryption_state_is_unknown() {
2631 let user_id = user_id!("@raclette:patate");
2632 let client = logged_in_base_client(Some(user_id)).await;
2633 let room_id_0 = room_id!("!r0");
2634 let room_id_1 = room_id!("!r1");
2635
2636 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2649
2650 let mut response = http::Response::new("0".to_owned());
2651
2652 {
2654 let not_encrypted_room = http::response::Room::new();
2655 let mut encrypted_room = http::response::Room::new();
2656 set_room_is_encrypted(&mut encrypted_room, user_id);
2657
2658 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2659 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2660 }
2661
2662 client
2663 .process_sliding_sync(
2664 &response,
2665 &requested_required_states,
2666 &client.state_store_lock().lock().await,
2667 )
2668 .await
2669 .expect("Failed to process sync");
2670
2671 assert_matches!(
2674 client.get_room(room_id_0).unwrap().encryption_state(),
2675 EncryptionState::Encrypted
2676 );
2677 assert_matches!(
2680 client.get_room(room_id_1).unwrap().encryption_state(),
2681 EncryptionState::Unknown
2682 );
2683 }
2684
2685 async fn membership(
2686 client: &BaseClient,
2687 room_id: &RoomId,
2688 user_id: &UserId,
2689 ) -> MembershipState {
2690 let room = client.get_room(room_id).expect("Room not found!");
2691 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2692 member.membership().clone()
2693 }
2694
2695 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2696 let room = client.get_room(room_id).expect("Room not found!");
2697 room.direct_targets()
2698 }
2699
2700 async fn create_dm(
2703 client: &BaseClient,
2704 room_id: &RoomId,
2705 my_id: &UserId,
2706 their_id: &UserId,
2707 other_state: MembershipState,
2708 ) {
2709 let mut room = http::response::Room::new();
2710 set_room_joined(&mut room, my_id);
2711
2712 match other_state {
2713 MembershipState::Join => {
2714 room.joined_count = Some(uint!(2));
2715 room.invited_count = None;
2716 }
2717
2718 MembershipState::Invite => {
2719 room.joined_count = Some(uint!(1));
2720 room.invited_count = Some(uint!(1));
2721 }
2722
2723 _ => {
2724 room.joined_count = Some(uint!(1));
2725 room.invited_count = None;
2726 }
2727 }
2728
2729 room.required_state.push(make_membership_event(their_id, other_state));
2730
2731 let mut response = response_with_room(room_id, room);
2732 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2733 client
2734 .process_sliding_sync(
2735 &response,
2736 &RequestedRequiredStates::default(),
2737 &client.state_store_lock().lock().await,
2738 )
2739 .await
2740 .expect("Failed to process sync");
2741 }
2742
2743 async fn update_room_membership(
2745 client: &BaseClient,
2746 room_id: &RoomId,
2747 user_id: &UserId,
2748 new_state: MembershipState,
2749 ) {
2750 let mut room = http::response::Room::new();
2751 room.required_state.push(make_membership_event(user_id, new_state));
2752 let response = response_with_room(room_id, room);
2753 client
2754 .process_sliding_sync(
2755 &response,
2756 &RequestedRequiredStates::default(),
2757 &client.state_store_lock().lock().await,
2758 )
2759 .await
2760 .expect("Failed to process sync");
2761 }
2762
2763 fn set_direct_with(
2764 response: &mut http::Response,
2765 user_id: OwnedUserId,
2766 room_ids: Vec<OwnedRoomId>,
2767 ) {
2768 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2769 BTreeMap::new();
2770 direct_content.insert(user_id.into(), room_ids);
2771 response
2772 .extensions
2773 .account_data
2774 .global
2775 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2776 }
2777
2778 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2779 let mut response = http::Response::new("5".to_owned());
2780 response.rooms.insert(room_id.to_owned(), room);
2781 response
2782 }
2783
2784 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2785 let mut room = http::response::Room::new();
2786
2787 let mut avatar_event_content = RoomAvatarEventContent::new();
2788 avatar_event_content.url = Some(avatar_uri.to_owned());
2789
2790 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2791
2792 room
2793 }
2794
2795 fn room_with_canonical_alias(
2796 room_alias_id: &RoomAliasId,
2797 user_id: &UserId,
2798 ) -> http::response::Room {
2799 let mut room = http::response::Room::new();
2800
2801 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2802 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2803
2804 room.required_state.push(make_state_event(
2805 user_id,
2806 "",
2807 canonical_alias_event_content,
2808 None,
2809 ));
2810
2811 room
2812 }
2813
2814 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2815 let mut room = http::response::Room::new();
2816
2817 let name_event_content = RoomNameEventContent::new(name.to_owned());
2818
2819 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2820
2821 room
2822 }
2823
2824 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2825 room.required_state.push(make_state_event(
2826 sender,
2827 "",
2828 RoomNameEventContent::new(name),
2829 None,
2830 ));
2831 }
2832
2833 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2834 let evt = Raw::new(&json!({
2838 "type": "m.room.member",
2839 "sender": inviter,
2840 "content": {
2841 "is_direct": true,
2842 "membership": "invite",
2843 },
2844 "state_key": invitee,
2845 }))
2846 .expect("Failed to make raw event")
2847 .cast_unchecked();
2848
2849 room.invite_state = Some(vec![evt]);
2850
2851 room.required_state.push(make_state_event(
2854 inviter,
2855 invitee.as_str(),
2856 RoomMemberEventContent::new(MembershipState::Invite),
2857 None,
2858 ));
2859 }
2860
2861 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2862 let evt = Raw::new(&json!({
2866 "type": "m.room.member",
2867 "sender": knocker,
2868 "content": {
2869 "is_direct": true,
2870 "membership": "knock",
2871 },
2872 "state_key": knocker,
2873 }))
2874 .expect("Failed to make raw event")
2875 .cast_unchecked();
2876
2877 room.invite_state = Some(vec![evt]);
2878 }
2879
2880 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2881 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2882 }
2883
2884 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2885 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2886 }
2887
2888 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2889 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2890 }
2891
2892 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2893 room.required_state.push(make_encryption_event(user_id));
2894 }
2895
2896 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2897 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2898 }
2899
2900 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2901 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2902 }
2903
2904 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2905 Raw::new(&json!({
2906 "type": content.event_type(),
2907 "content": content,
2908 }))
2909 .expect("Failed to create account data event")
2910 .cast_unchecked()
2911 }
2912
2913 fn make_state_event<C: StateEventContent, E>(
2914 sender: &UserId,
2915 state_key: &str,
2916 content: C,
2917 prev_content: Option<C>,
2918 ) -> Raw<E> {
2919 let unsigned = if let Some(prev_content) = prev_content {
2920 json!({ "prev_content": prev_content })
2921 } else {
2922 json!({})
2923 };
2924
2925 Raw::new(&json!({
2926 "type": content.event_type(),
2927 "state_key": state_key,
2928 "content": content,
2929 "event_id": event_id!("$evt"),
2930 "sender": sender,
2931 "origin_server_ts": 10,
2932 "unsigned": unsigned,
2933 }))
2934 .expect("Failed to create state event")
2935 .cast_unchecked()
2936 }
2937
2938 fn make_profile_update(field: ProfileFieldName, value: serde_json::Value) -> UserProfileUpdate {
2939 let mut changes = UserProfileChanges::new();
2940 changes.updated.insert(field, value);
2941 UserProfileUpdate::Updated(changes)
2942 }
2943}