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 self.notify_global_profile_updates(
225 extensions.profiles.users.keys().cloned().collect(),
226 state_store_guard,
227 )?;
228
229 let mut context = processors::Context::default();
230
231 processors::room::display_name::update_for_rooms(
234 &mut context,
235 &room_updates,
236 &self.state_store,
237 )
238 .await;
239
240 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
242
243 Ok(SyncResponse {
244 rooms: room_updates,
245 notifications,
246 presence: Default::default(),
247 account_data: extensions.account_data.global.clone(),
248 to_device: Default::default(),
249 })
250 }
251
252 #[doc(hidden)]
255 pub async fn process_sliding_sync_receipts_extension_for_room(
256 &self,
257 room_id: &OwnedRoomId,
258 response: &http::Response,
259 state_store_guard: &MutexGuard<'_, ()>,
260 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
261 let mut context = processors::Context::default();
262
263 let mut save_context = false;
264
265 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
267 response.extensions.receipts.rooms.get(room_id)
268 {
269 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
270 &mut context,
271 room_id,
272 receipt_ephemeral_event,
273 );
274 save_context = true;
275 Some(receipt_ephemeral_event.clone())
276 } else {
277 None
278 };
279
280 if save_context {
282 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
283 }
284
285 Ok(receipt_ephemeral_event)
286 }
287}
288
289#[cfg(all(test, not(target_family = "wasm")))]
290mod tests {
291 use std::collections::{BTreeMap, HashSet};
292
293 use assert_matches::assert_matches;
294 use matrix_sdk_test::async_test;
295 use ruma::{
296 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
297 api::client::sync::sync_events::UnreadNotificationsCount,
298 assign, event_id,
299 events::{
300 GlobalAccountDataEventContent, StateEventContent, StateEventType,
301 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
302 room::{
303 avatar::RoomAvatarEventContent,
304 canonical_alias::RoomCanonicalAliasEventContent,
305 encryption::RoomEncryptionEventContent,
306 member::{MembershipState, RoomMemberEventContent},
307 name::RoomNameEventContent,
308 pinned_events::RoomPinnedEventsEventContent,
309 },
310 },
311 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
312 profile::{ProfileFieldName, UserProfileChanges, UserProfileUpdate},
313 room_alias_id, room_id,
314 serde::Raw,
315 uint, user_id,
316 };
317 use serde_json::json;
318 #[cfg(feature = "unstable-msc4426")]
319 use stream_assert::{assert_pending, assert_ready};
320
321 use super::http;
322 use crate::{
323 BaseClient, DmRoomDefinition, EncryptionState, RequestedRequiredStates,
324 RoomInfoNotableUpdate, RoomState, SessionMeta,
325 client::ThreadingSupport,
326 room::{RoomHero, RoomInfoNotableUpdateReasons},
327 store::{RoomLoadSettings, StoreConfig},
328 test_utils::logged_in_base_client,
329 };
330
331 #[async_test]
332 async fn test_invited_state_without_update_emits_invited_room() {
333 let client = logged_in_base_client(None).await;
334 let room_id = room_id!("!invite:e.uk");
335 let user_id = client.session_meta().unwrap().user_id.to_owned();
336
337 let mut room = http::response::Room::new();
338 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Invite));
339
340 let response = response_with_room(room_id, room);
341
342 let sync_resp = client
343 .process_sliding_sync(
344 &response,
345 &RequestedRequiredStates::default(),
346 &client.state_store_lock().lock().await,
347 )
348 .await
349 .unwrap();
350
351 assert!(sync_resp.rooms.invited.contains_key(room_id));
352 }
353
354 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
355 use ruma::events::AnyStrippedStateEvent;
356
357 fn invite_state_for(
358 user_id: &UserId,
359 membership: MembershipState,
360 ) -> Vec<Raw<AnyStrippedStateEvent>> {
361 let content = RoomMemberEventContent::new(membership);
362
363 let raw: Raw<AnyStrippedStateEvent> = Raw::from_json_string(
364 serde_json::json!({
365 "type": "m.room.member",
366 "state_key": user_id,
367 "content": content,
368 })
369 .to_string(),
370 )
371 .unwrap();
372
373 vec![raw]
374 }
375
376 #[async_test]
377 async fn test_knocked_state_emits_invited_room() {
378 let client = logged_in_base_client(None).await;
379 let room_id = room_id!("!knock:e.uk");
380 let user_id = client.session_meta().unwrap().user_id.to_owned();
381
382 let mut room = http::response::Room::new();
383 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Knock));
384
385 let response = response_with_room(room_id, room);
386
387 let sync_resp = client
388 .process_sliding_sync(
389 &response,
390 &RequestedRequiredStates::default(),
391 &client.state_store_lock().lock().await,
392 )
393 .await
394 .unwrap();
395
396 assert!(sync_resp.rooms.invited.contains_key(room_id));
398 }
399
400 #[async_test]
401 async fn test_notification_count_set() {
402 let client = logged_in_base_client(None).await;
403
404 let mut response = http::Response::new("42".to_owned());
405 let room_id = room_id!("!room:example.org");
406 let count = assign!(UnreadNotificationsCount::default(), {
407 highlight_count: Some(uint!(13)),
408 notification_count: Some(uint!(37)),
409 });
410
411 response.rooms.insert(
412 room_id.to_owned(),
413 assign!(http::response::Room::new(), {
414 unread_notifications: count.clone()
415 }),
416 );
417
418 let sync_response = client
419 .process_sliding_sync(
420 &response,
421 &RequestedRequiredStates::default(),
422 &client.state_store_lock().lock().await,
423 )
424 .await
425 .expect("Failed to process sync");
426
427 let room = sync_response.rooms.joined.get(room_id).unwrap();
429 assert_eq!(room.unread_notifications, count.clone().into());
430
431 let room = client.get_room(room_id).expect("found room");
433 assert_eq!(room.unread_notification_counts(), count.into());
434 }
435
436 #[async_test]
437 async fn test_can_process_empty_sliding_sync_response() {
438 let client = logged_in_base_client(None).await;
439 let empty_response = http::Response::new("5".to_owned());
440 client
441 .process_sliding_sync(
442 &empty_response,
443 &RequestedRequiredStates::default(),
444 &client.state_store_lock().lock().await,
445 )
446 .await
447 .expect("Failed to process sync");
448 }
449
450 #[async_test]
451 async fn test_profiles_extension_is_persisted_from_sliding_sync() {
452 let client = logged_in_base_client(None).await;
453
454 let alice = user_id!("@alice:e.uk");
455 let bob = user_id!("@bob:e.uk");
456
457 let mut response = http::Response::new("0".to_owned());
460 response.extensions.profiles.users.insert(
461 alice.to_owned(),
462 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
463 );
464 response.extensions.profiles.users.insert(
465 bob.to_owned(),
466 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
467 );
468
469 client
471 .process_sliding_sync(
472 &response,
473 &RequestedRequiredStates::default(),
474 &client.state_store_lock().lock().await,
475 )
476 .await
477 .expect("Failed to process sync");
478
479 let store = client.state_store();
481
482 let alice_profile = store
483 .get_global_profile(alice)
484 .await
485 .expect("Failed to read profile")
486 .expect("Alice's profile should be saved");
487 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
488 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice")));
489
490 let bob_profile = store
491 .get_global_profile(bob)
492 .await
493 .expect("Failed to read profile")
494 .expect("Bob's profile should be saved");
495 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
496 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
497
498 let mut response = http::Response::new("1".to_owned());
500 response.extensions.profiles.users.insert(
501 alice.to_owned(),
502 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
503 );
504
505 client
506 .process_sliding_sync(
507 &response,
508 &RequestedRequiredStates::default(),
509 &client.state_store_lock().lock().await,
510 )
511 .await
512 .expect("Failed to process sync");
513
514 let alice_profile = store
516 .get_global_profile(alice)
517 .await
518 .expect("Failed to read profile")
519 .expect("Alice's profile should be saved");
520 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
521 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice Updated")));
522
523 let bob_profile = store
524 .get_global_profile(bob)
525 .await
526 .expect("Failed to read profile")
527 .expect("Bob's profile should still be saved");
528 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
529 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
530 }
531
532 #[async_test]
533 async fn test_profiles_extension_broadcasts_global_profile_updates() {
534 let client = logged_in_base_client(None).await;
535
536 let alice = user_id!("@alice:e.uk");
537 let bob = user_id!("@bob:e.uk");
538
539 let mut global_profile_updates = client.subscribe_to_global_profile_updates();
541
542 let mut response = http::Response::new("0".to_owned());
544 response.extensions.profiles.users.insert(
545 alice.to_owned(),
546 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
547 );
548 response.extensions.profiles.users.insert(
549 bob.to_owned(),
550 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
551 );
552 client
553 .process_sliding_sync(
554 &response,
555 &RequestedRequiredStates::default(),
556 &client.state_store_lock().lock().await,
557 )
558 .await
559 .expect("Failed to process sync");
560
561 let users =
563 global_profile_updates.recv().await.expect("should receive a global profile update");
564 assert_eq!(users.len(), 2);
565 assert!(users.contains(alice));
566 assert!(users.contains(bob));
567
568 let mut response = http::Response::new("1".to_owned());
570 response.extensions.profiles.users.insert(
571 alice.to_owned(),
572 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
573 );
574 client
575 .process_sliding_sync(
576 &response,
577 &RequestedRequiredStates::default(),
578 &client.state_store_lock().lock().await,
579 )
580 .await
581 .expect("Failed to process sync");
582
583 let users =
585 global_profile_updates.recv().await.expect("should receive a global profile update");
586 assert_eq!(users.len(), 1);
587 assert!(users.contains(alice));
588 }
589
590 #[async_test]
591 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
592 let client = logged_in_base_client(None).await;
594 let room_id = room_id!("!r:e.uk");
595
596 let mut room = http::response::Room::new();
599 room.joined_count = Some(uint!(41));
600 let response = response_with_room(room_id, room);
601 let sync_resp = client
602 .process_sliding_sync(
603 &response,
604 &RequestedRequiredStates::default(),
605 &client.state_store_lock().lock().await,
606 )
607 .await
608 .expect("Failed to process sync");
609
610 let client_room = client.get_room(room_id).expect("No room found");
612 assert_eq!(client_room.room_id(), room_id);
613 assert_eq!(client_room.joined_members_count(), 41);
614 assert_eq!(client_room.state(), RoomState::Joined);
615
616 assert!(sync_resp.rooms.joined.contains_key(room_id));
618 assert!(!sync_resp.rooms.left.contains_key(room_id));
619 assert!(!sync_resp.rooms.invited.contains_key(room_id));
620 }
621
622 #[async_test]
623 async fn test_missing_room_name_event() {
624 let client = logged_in_base_client(None).await;
626 let room_id = room_id!("!r:e.uk");
627
628 let mut room = http::response::Room::new();
631 room.name = Some("little room".to_owned());
632 let response = response_with_room(room_id, room);
633 let sync_resp = client
634 .process_sliding_sync(
635 &response,
636 &RequestedRequiredStates::default(),
637 &client.state_store_lock().lock().await,
638 )
639 .await
640 .expect("Failed to process sync");
641
642 let client_room = client.get_room(room_id).expect("No room found");
644 assert!(client_room.name().is_none());
645 assert_eq!(
646 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
647 "Empty Room"
648 );
649 assert_eq!(client_room.state(), RoomState::Joined);
650
651 assert!(sync_resp.rooms.joined.contains_key(room_id));
653 assert!(!sync_resp.rooms.left.contains_key(room_id));
654 assert!(!sync_resp.rooms.invited.contains_key(room_id));
655 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
656 }
657
658 #[async_test]
659 async fn test_room_name_event() {
660 let client = logged_in_base_client(None).await;
662 let room_id = room_id!("!r:e.uk");
663
664 let mut room = http::response::Room::new();
667
668 room.name = Some("little room".to_owned());
669 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
670
671 let response = response_with_room(room_id, room);
672 client
673 .process_sliding_sync(
674 &response,
675 &RequestedRequiredStates::default(),
676 &client.state_store_lock().lock().await,
677 )
678 .await
679 .expect("Failed to process sync");
680
681 let client_room = client.get_room(room_id).expect("No room found");
683 assert_eq!(client_room.name().as_deref(), Some("The Name"));
684 assert_eq!(
685 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
686 "The Name"
687 );
688 }
689
690 #[async_test]
691 async fn test_missing_invited_room_name_event() {
692 let client = logged_in_base_client(None).await;
694 let room_id = room_id!("!r:e.uk");
695 let user_id = user_id!("@w:e.uk");
696 let inviter = user_id!("@john:mastodon.org");
697
698 let mut room = http::response::Room::new();
701 set_room_invited(&mut room, inviter, user_id);
702 room.name = Some("name from sliding sync response".to_owned());
703 let response = response_with_room(room_id, room);
704 let sync_resp = client
705 .process_sliding_sync(
706 &response,
707 &RequestedRequiredStates::default(),
708 &client.state_store_lock().lock().await,
709 )
710 .await
711 .expect("Failed to process sync");
712
713 let client_room = client.get_room(room_id).expect("No room found");
715 assert!(client_room.name().is_none());
716
717 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
719
720 assert_eq!(client_room.state(), RoomState::Invited);
721
722 assert!(!sync_resp.rooms.joined.contains_key(room_id));
724 assert!(!sync_resp.rooms.left.contains_key(room_id));
725 assert!(sync_resp.rooms.invited.contains_key(room_id));
726 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
727 }
728
729 #[async_test]
730 async fn test_invited_room_name_event() {
731 let client = logged_in_base_client(None).await;
733 let room_id = room_id!("!r:e.uk");
734 let user_id = user_id!("@w:e.uk");
735 let inviter = user_id!("@john:mastodon.org");
736
737 let mut room = http::response::Room::new();
740
741 set_room_invited(&mut room, inviter, user_id);
742
743 room.name = Some("name from sliding sync response".to_owned());
744 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
745
746 let response = response_with_room(room_id, room);
747 client
748 .process_sliding_sync(
749 &response,
750 &RequestedRequiredStates::default(),
751 &client.state_store_lock().lock().await,
752 )
753 .await
754 .expect("Failed to process sync");
755
756 let client_room = client.get_room(room_id).expect("No room found");
758 assert_eq!(client_room.name().as_deref(), Some("The Name"));
759 assert_eq!(
760 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
761 "The Name"
762 );
763 }
764
765 #[async_test]
766 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
767 let client = logged_in_base_client(None).await;
769 let room_id = room_id!("!r:e.uk");
770 let user_id = client.session_meta().unwrap().user_id.to_owned();
771
772 let mut room = http::response::Room::new();
775 set_room_knocked(&mut room, &user_id);
776
777 let response = response_with_room(room_id, room);
778 client
779 .process_sliding_sync(
780 &response,
781 &RequestedRequiredStates::default(),
782 &client.state_store_lock().lock().await,
783 )
784 .await
785 .expect("Failed to process sync");
786
787 let client_room = client.get_room(room_id).expect("No room found");
789 assert_eq!(client_room.state(), RoomState::Knocked);
790 }
791
792 #[async_test]
793 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
794 {
795 let client = logged_in_base_client(None).await;
797 let room_id = room_id!("!r:e.uk");
798 let user_id = user_id!("@w:e.uk");
799
800 let mut room = http::response::Room::new();
802 set_room_knocked(&mut room, user_id);
803
804 let response = response_with_room(room_id, room);
805 client
806 .process_sliding_sync(
807 &response,
808 &RequestedRequiredStates::default(),
809 &client.state_store_lock().lock().await,
810 )
811 .await
812 .expect("Failed to process sync");
813
814 let client_room = client.get_room(room_id).expect("No room found");
817 assert_eq!(client_room.state(), RoomState::Invited);
818 }
819
820 #[async_test]
821 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
822 {
823 let client = logged_in_base_client(None).await;
825 let room_id = room_id!("!r:e.uk");
826 let user_id = client.session_meta().unwrap().user_id.to_owned();
827
828 let mut room = http::response::Room::new();
830 let event = Raw::new(&json!({
831 "type": "m.room.member",
832 "sender": user_id,
833 "content": {
834 "is_direct": true,
835 "membership": "join",
836 },
837 "state_key": user_id,
838 }))
839 .expect("Failed to make raw event")
840 .cast_unchecked();
841 room.invite_state = Some(vec![event]);
842
843 let response = response_with_room(room_id, room);
844 client
845 .process_sliding_sync(
846 &response,
847 &RequestedRequiredStates::default(),
848 &client.state_store_lock().lock().await,
849 )
850 .await
851 .expect("Failed to process sync");
852
853 let client_room = client.get_room(room_id).expect("No room found");
855 assert_eq!(client_room.state(), RoomState::Invited);
856 }
857
858 #[async_test]
859 async fn test_left_a_room_from_required_state_event() {
860 let client = logged_in_base_client(None).await;
862 let room_id = room_id!("!r:e.uk");
863 let user_id = user_id!("@u:e.uk");
864
865 let mut room = http::response::Room::new();
867 set_room_joined(&mut room, user_id);
868 let response = response_with_room(room_id, room);
869 client
870 .process_sliding_sync(
871 &response,
872 &RequestedRequiredStates::default(),
873 &client.state_store_lock().lock().await,
874 )
875 .await
876 .expect("Failed to process sync");
877 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
878
879 let mut room = http::response::Room::new();
881 set_room_left(&mut room, user_id);
882 let response = response_with_room(room_id, room);
883 let sync_resp = client
884 .process_sliding_sync(
885 &response,
886 &RequestedRequiredStates::default(),
887 &client.state_store_lock().lock().await,
888 )
889 .await
890 .expect("Failed to process sync");
891
892 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
894
895 assert!(!sync_resp.rooms.joined.contains_key(room_id));
897 assert!(sync_resp.rooms.left.contains_key(room_id));
898 assert!(!sync_resp.rooms.invited.contains_key(room_id));
899 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
900 }
901
902 #[async_test]
903 async fn test_kick_or_ban_updates_room_to_left() {
904 for membership in [MembershipState::Leave, MembershipState::Ban] {
905 let room_id = room_id!("!r:e.uk");
906 let user_a_id = user_id!("@a:e.uk");
907 let user_b_id = user_id!("@b:e.uk");
908 let client = logged_in_base_client(Some(user_a_id)).await;
909
910 let mut room = http::response::Room::new();
912 set_room_joined(&mut room, user_a_id);
913 let response = response_with_room(room_id, room);
914 client
915 .process_sliding_sync(
916 &response,
917 &RequestedRequiredStates::default(),
918 &client.state_store_lock().lock().await,
919 )
920 .await
921 .expect("Failed to process sync");
922 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
923
924 let mut room = http::response::Room::new();
926 room.required_state.push(make_state_event(
927 user_b_id,
928 user_a_id.as_str(),
929 RoomMemberEventContent::new(membership.clone()),
930 None,
931 ));
932 let response = response_with_room(room_id, room);
933 let sync_resp = client
934 .process_sliding_sync(
935 &response,
936 &RequestedRequiredStates::default(),
937 &client.state_store_lock().lock().await,
938 )
939 .await
940 .expect("Failed to process sync");
941
942 match membership {
943 MembershipState::Leave => {
944 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
946 }
947 MembershipState::Ban => {
948 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
950 }
951 _ => panic!("Unexpected membership state found: {membership}"),
952 }
953
954 assert!(!sync_resp.rooms.joined.contains_key(room_id));
956 assert!(sync_resp.rooms.left.contains_key(room_id));
957 assert!(!sync_resp.rooms.invited.contains_key(room_id));
958 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
959 }
960 }
961
962 #[async_test]
963 async fn test_left_a_room_from_timeline_state_event() {
964 let client = logged_in_base_client(None).await;
966 let room_id = room_id!("!r:e.uk");
967 let user_id = user_id!("@u:e.uk");
968
969 let mut room = http::response::Room::new();
971 set_room_joined(&mut room, user_id);
972 let response = response_with_room(room_id, room);
973 client
974 .process_sliding_sync(
975 &response,
976 &RequestedRequiredStates::default(),
977 &client.state_store_lock().lock().await,
978 )
979 .await
980 .expect("Failed to process sync");
981 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
982
983 let mut room = http::response::Room::new();
985 set_room_left_as_timeline_event(&mut room, user_id);
986 let response = response_with_room(room_id, room);
987 client
988 .process_sliding_sync(
989 &response,
990 &RequestedRequiredStates::default(),
991 &client.state_store_lock().lock().await,
992 )
993 .await
994 .expect("Failed to process sync");
995
996 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
998 }
999
1000 #[async_test]
1001 async fn test_can_be_reinvited_to_a_left_room() {
1002 let client = logged_in_base_client(None).await;
1006 let room_id = room_id!("!r:e.uk");
1007 let user_id = user_id!("@u:e.uk");
1008
1009 let mut room = http::response::Room::new();
1011 set_room_joined(&mut room, user_id);
1012 let response = response_with_room(room_id, room);
1013 client
1014 .process_sliding_sync(
1015 &response,
1016 &RequestedRequiredStates::default(),
1017 &client.state_store_lock().lock().await,
1018 )
1019 .await
1020 .expect("Failed to process sync");
1021 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1023
1024 let mut room = http::response::Room::new();
1026 set_room_left(&mut room, user_id);
1027 let response = response_with_room(room_id, room);
1028 client
1029 .process_sliding_sync(
1030 &response,
1031 &RequestedRequiredStates::default(),
1032 &client.state_store_lock().lock().await,
1033 )
1034 .await
1035 .expect("Failed to process sync");
1036 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1038
1039 let mut room = http::response::Room::new();
1041 set_room_invited(&mut room, user_id, user_id);
1042 let response = response_with_room(room_id, room);
1043 client
1044 .process_sliding_sync(
1045 &response,
1046 &RequestedRequiredStates::default(),
1047 &client.state_store_lock().lock().await,
1048 )
1049 .await
1050 .expect("Failed to process sync");
1051
1052 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1054 }
1055
1056 #[async_test]
1057 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1058 let room_id = room_id!("!r:e.uk");
1059 let user_a_id = user_id!("@a:e.uk");
1060 let user_b_id = user_id!("@b:e.uk");
1061
1062 let client = logged_in_base_client(None).await;
1064 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1065
1066 assert!(
1068 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1069 );
1070 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1071
1072 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1074
1075 assert!(
1079 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1080 );
1081 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1082 }
1083
1084 #[async_test]
1085 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
1086 {
1087 let room_id = room_id!("!r:e.uk");
1088 let user_a_id = user_id!("@a:e.uk");
1089 let user_b_id = user_id!("@b:e.uk");
1090
1091 let client = logged_in_base_client(None).await;
1093 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1094
1095 assert!(
1097 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1098 );
1099 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1100
1101 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1103
1104 assert!(
1108 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1109 );
1110 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1111 }
1112
1113 #[async_test]
1114 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1115 let room_id = room_id!("!r:bar.org");
1116 let user_a_id = user_id!("@a:bar.org");
1117 let user_b_id = user_id!("@b:bar.org");
1118
1119 let client = logged_in_base_client(None).await;
1121 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1122
1123 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1125
1126 assert!(
1128 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1129 );
1130 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1131
1132 let room = client.get_room(room_id).unwrap();
1133
1134 assert_eq!(room.active_members_count(), 2);
1135 assert_eq!(room.joined_members_count(), 2);
1136 assert_eq!(room.invited_members_count(), 0);
1137 }
1138
1139 #[async_test]
1140 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1141 let room_id = room_id!("!r:bar.org");
1142 let user_a_id = user_id!("@a:bar.org");
1143 let user_b_id = user_id!("@b:bar.org");
1144
1145 let client = logged_in_base_client(None).await;
1147 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1148
1149 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1151
1152 assert!(
1154 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1155 );
1156 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1157
1158 let room = client.get_room(room_id).unwrap();
1159
1160 assert_eq!(room.active_members_count(), 2);
1161 assert_eq!(room.joined_members_count(), 1);
1162 assert_eq!(room.invited_members_count(), 1);
1163 }
1164
1165 #[async_test]
1166 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1167 let client = logged_in_base_client(None).await;
1169 let room_id = room_id!("!r:e.uk");
1170
1171 let room = {
1173 let mut room = http::response::Room::new();
1174 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1175
1176 room
1177 };
1178 let response = response_with_room(room_id, room);
1179 client
1180 .process_sliding_sync(
1181 &response,
1182 &RequestedRequiredStates::default(),
1183 &client.state_store_lock().lock().await,
1184 )
1185 .await
1186 .expect("Failed to process sync");
1187
1188 let client_room = client.get_room(room_id).expect("No room found");
1190 assert_eq!(
1191 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1192 "med1"
1193 );
1194 }
1195
1196 #[async_test]
1197 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1198 let client = logged_in_base_client(None).await;
1200 let room_id = room_id!("!r:e.uk");
1201
1202 let room = {
1206 let mut room = http::response::Room::new();
1207 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1208
1209 room
1210 };
1211 let response = response_with_room(room_id, room);
1212 client
1213 .process_sliding_sync(
1214 &response,
1215 &RequestedRequiredStates::default(),
1216 &client.state_store_lock().lock().await,
1217 )
1218 .await
1219 .expect("Failed to process sync");
1220
1221 let client_room = client.get_room(room_id).expect("No room found");
1223 assert_eq!(
1224 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1225 "med1"
1226 );
1227
1228 let room = http::response::Room::new();
1232 let response = response_with_room(room_id, room);
1233 client
1234 .process_sliding_sync(
1235 &response,
1236 &RequestedRequiredStates::default(),
1237 &client.state_store_lock().lock().await,
1238 )
1239 .await
1240 .expect("Failed to process sync");
1241
1242 let client_room = client.get_room(room_id).expect("No room found");
1244 assert_eq!(
1245 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1246 "med1"
1247 );
1248
1249 let room = {
1253 let mut room = http::response::Room::new();
1254 room.avatar = JsOption::Null;
1255
1256 room
1257 };
1258 let response = response_with_room(room_id, room);
1259 client
1260 .process_sliding_sync(
1261 &response,
1262 &RequestedRequiredStates::default(),
1263 &client.state_store_lock().lock().await,
1264 )
1265 .await
1266 .expect("Failed to process sync");
1267
1268 let client_room = client.get_room(room_id).expect("No room found");
1270 assert!(client_room.avatar_url().is_none());
1271 }
1272
1273 #[async_test]
1274 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1275 let client = logged_in_base_client(None).await;
1277 let room_id = room_id!("!r:e.uk");
1278 let user_id = user_id!("@u:e.uk");
1279
1280 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1282 let response = response_with_room(room_id, room);
1283 client
1284 .process_sliding_sync(
1285 &response,
1286 &RequestedRequiredStates::default(),
1287 &client.state_store_lock().lock().await,
1288 )
1289 .await
1290 .expect("Failed to process sync");
1291
1292 let client_room = client.get_room(room_id).expect("No room found");
1294 assert_eq!(
1295 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1296 "med1"
1297 );
1298 }
1299
1300 #[async_test]
1301 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1302 let client = logged_in_base_client(None).await;
1304 let room_id = room_id!("!r:e.uk");
1305 let user_id = user_id!("@u:e.uk");
1306
1307 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1308
1309 let mut room = http::response::Room::new();
1311 set_room_invited(&mut room, user_id, user_id);
1312 let response = response_with_room(room_id, room);
1313 let sync_resp = client
1314 .process_sliding_sync(
1315 &response,
1316 &RequestedRequiredStates::default(),
1317 &client.state_store_lock().lock().await,
1318 )
1319 .await
1320 .expect("Failed to process sync");
1321
1322 let client_room = client.get_room(room_id).expect("No room found");
1324 assert_eq!(client_room.room_id(), room_id);
1325 assert_eq!(client_room.state(), RoomState::Invited);
1326
1327 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1329 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1330
1331 assert_matches!(
1332 room_info_notable_update.recv().await,
1333 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1334 assert_eq!(received_room_id, room_id);
1335 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1337 }
1338 );
1339 assert_matches!(
1340 room_info_notable_update.recv().await,
1341 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1342 assert_eq!(received_room_id, room_id);
1343 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1345 }
1346 );
1347 assert!(room_info_notable_update.is_empty());
1348 }
1349
1350 #[async_test]
1351 async fn test_knock_room_is_added_to_client_and_knock_list() {
1352 let client = logged_in_base_client(None).await;
1354 let room_id = room_id!("!r:e.uk");
1355 let user_id = user_id!("@u:e.uk");
1356
1357 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1358
1359 let mut room = http::response::Room::new();
1361 set_room_knocked(&mut room, user_id);
1362 let response = response_with_room(room_id, room);
1363 let sync_resp = client
1364 .process_sliding_sync(
1365 &response,
1366 &RequestedRequiredStates::default(),
1367 &client.state_store_lock().lock().await,
1368 )
1369 .await
1370 .expect("Failed to process sync");
1371
1372 let client_room = client.get_room(room_id).expect("No room found");
1374 assert_eq!(client_room.room_id(), room_id);
1375 assert_eq!(client_room.state(), RoomState::Knocked);
1376
1377 assert!(!sync_resp.rooms.knocked[room_id].knock_state.is_empty());
1379 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1380
1381 assert_matches!(
1382 room_info_notable_update.recv().await,
1383 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1384 assert_eq!(received_room_id, room_id);
1385 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1387 }
1388 );
1389 assert_matches!(
1390 room_info_notable_update.recv().await,
1391 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1392 assert_eq!(received_room_id, room_id);
1393 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1395 }
1396 );
1397 assert!(room_info_notable_update.is_empty());
1398 }
1399
1400 #[async_test]
1401 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1402 let client = logged_in_base_client(None).await;
1404 let room_id = room_id!("!r:e.uk");
1405 let user_id = user_id!("@u:e.uk");
1406
1407 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1409 set_room_invited(&mut room, user_id, user_id);
1410 let response = response_with_room(room_id, room);
1411 client
1412 .process_sliding_sync(
1413 &response,
1414 &RequestedRequiredStates::default(),
1415 &client.state_store_lock().lock().await,
1416 )
1417 .await
1418 .expect("Failed to process sync");
1419
1420 let client_room = client.get_room(room_id).expect("No room found");
1422 assert_eq!(
1423 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1424 "med1"
1425 );
1426 }
1427
1428 #[async_test]
1429 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1430 {
1431 let client = logged_in_base_client(None).await;
1433 let room_id = room_id!("!r:e.uk");
1434 let user_id = user_id!("@u:e.uk");
1435 let room_alias_id = room_alias_id!("#myroom:e.uk");
1436
1437 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1439 set_room_invited(&mut room, user_id, user_id);
1440 let response = response_with_room(room_id, room);
1441 client
1442 .process_sliding_sync(
1443 &response,
1444 &RequestedRequiredStates::default(),
1445 &client.state_store_lock().lock().await,
1446 )
1447 .await
1448 .expect("Failed to process sync");
1449
1450 let client_room = client.get_room(room_id).expect("No room found");
1452 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1453 }
1454
1455 #[async_test]
1456 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1457 let client = logged_in_base_client(None).await;
1459 let room_id = room_id!("!r:e.uk");
1460 let user_id = user_id!("@u:e.uk");
1461 let room_alias_id = room_alias_id!("#myroom:e.uk");
1462
1463 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1466 room.name = Some("This came from the server".to_owned());
1467 let response = response_with_room(room_id, room);
1468 client
1469 .process_sliding_sync(
1470 &response,
1471 &RequestedRequiredStates::default(),
1472 &client.state_store_lock().lock().await,
1473 )
1474 .await
1475 .expect("Failed to process sync");
1476
1477 let client_room = client.get_room(room_id).expect("No room found");
1479 assert_eq!(
1480 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1481 "myroom"
1482 );
1483 assert!(client_room.name().is_none());
1484 }
1485
1486 #[async_test]
1487 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1488 let client = logged_in_base_client(None).await;
1489 let user_id = user_id!("@u:e.uk");
1490 let room_id = room_id!("!r:e.uk");
1491
1492 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1493
1494 let room = room_with_name("Hello World", user_id);
1495 let response = response_with_room(room_id, room);
1496 client
1497 .process_sliding_sync(
1498 &response,
1499 &RequestedRequiredStates::default(),
1500 &client.state_store_lock().lock().await,
1501 )
1502 .await
1503 .expect("Failed to process sync");
1504
1505 let room = client.get_room(room_id).expect("No room found");
1506 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1507
1508 assert_matches!(
1509 room_info_notable_update.recv().await,
1510 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1511 assert_eq!(received_room_id, room_id);
1512 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1513 }
1514 );
1515 assert_matches!(
1516 room_info_notable_update.recv().await,
1517 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1518 assert_eq!(received_room_id, room_id);
1519 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1521 }
1522 );
1523 assert!(room_info_notable_update.is_empty());
1524 }
1525
1526 #[async_test]
1527 async fn test_display_name_is_persisted_from_sliding_sync() {
1528 let user_id = user_id!("@u:e.uk");
1529 let room_id = room_id!("!r:e.uk");
1530 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1531 let state_store;
1532
1533 {
1534 let client = {
1535 let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1536 state_store = store.state_store.clone();
1537
1538 let client =
1539 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1540 client
1541 .activate(
1542 session_meta.clone(),
1543 RoomLoadSettings::default(),
1544 #[cfg(feature = "e2e-encryption")]
1545 None,
1546 )
1547 .await
1548 .expect("`activate` failed!");
1549
1550 client
1551 };
1552
1553 let room = room_with_name("Hello World", user_id);
1556 let response = response_with_room(room_id, room);
1557 client
1558 .process_sliding_sync(
1559 &response,
1560 &RequestedRequiredStates::default(),
1561 &client.state_store_lock().lock().await,
1562 )
1563 .await
1564 .expect("Failed to process sync");
1565
1566 let room = client.get_room(room_id).expect("No room found");
1567 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1568 }
1569
1570 {
1571 let client = {
1572 let mut store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1573 store.state_store = state_store;
1574 let client =
1575 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1576 client
1577 .activate(
1578 session_meta,
1579 RoomLoadSettings::default(),
1580 #[cfg(feature = "e2e-encryption")]
1581 None,
1582 )
1583 .await
1584 .expect("`activate` failed!");
1585
1586 client
1587 };
1588
1589 let room = client.get_room(room_id).expect("No room found");
1590 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1591 }
1592 }
1593
1594 #[async_test]
1595 async fn test_compute_heroes_from_sliding_sync() {
1596 let client = logged_in_base_client(None).await;
1598 let room_id = room_id!("!r:e.uk");
1599 let gordon = owned_user_id!("@gordon:e.uk");
1600 let alice = owned_user_id!("@alice:e.uk");
1601
1602 let mut room = http::response::Room::new();
1605 room.heroes = Some(vec![
1606 assign!(http::response::Hero::new(gordon), {
1607 name: Some("Gordon".to_owned()),
1608 }),
1609 assign!(http::response::Hero::new(alice), {
1610 name: Some("Alice".to_owned()),
1611 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1612 }),
1613 ]);
1614 let response = response_with_room(room_id, room);
1615 let _sync_resp = client
1616 .process_sliding_sync(
1617 &response,
1618 &RequestedRequiredStates::default(),
1619 &client.state_store_lock().lock().await,
1620 )
1621 .await
1622 .expect("Failed to process sync");
1623
1624 let client_room = client.get_room(room_id).expect("No room found");
1626 assert_eq!(client_room.room_id(), room_id);
1627 assert_eq!(client_room.state(), RoomState::Joined);
1628
1629 assert_eq!(
1631 client_room.clone_info().summary.heroes(),
1632 &[
1633 RoomHero {
1634 user_id: owned_user_id!("@gordon:e.uk"),
1635 display_name: Some("Gordon".to_owned()),
1636 avatar_url: None
1637 },
1638 RoomHero {
1639 user_id: owned_user_id!("@alice:e.uk"),
1640 display_name: Some("Alice".to_owned()),
1641 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1642 },
1643 ]
1644 );
1645 }
1646
1647 #[cfg(feature = "unstable-msc4426")]
1648 #[async_test]
1649 async fn test_hero_global_profile_update_triggers_notable_update() {
1650 let client = logged_in_base_client(None).await;
1651 let room_id = room_id!("!r:e.uk");
1652 let alice = owned_user_id!("@alice:e.uk");
1653
1654 let mut room = http::response::Room::new();
1656 room.heroes = Some(vec![assign!(http::response::Hero::new(alice.clone()), {
1657 name: Some("Alice".to_owned()),
1658 })]);
1659 let response = response_with_room(room_id, room);
1660 client
1661 .process_sliding_sync(
1662 &response,
1663 &RequestedRequiredStates::default(),
1664 &client.state_store_lock().lock().await,
1665 )
1666 .await
1667 .expect("Failed to process sync");
1668
1669 let room = client.get_room(room_id).expect("The room should be known");
1670 let mut room_info_subscriber = room.subscribe_info();
1671 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1672
1673 assert_pending!(room_info_subscriber);
1674
1675 let mut response = http::Response::new("1".to_owned());
1677 response.extensions.profiles.users.insert(
1678 alice.clone(),
1679 make_profile_update(ProfileFieldName::Status, json!({ "text": "Away", "emoji": "🌴" })),
1680 );
1681 client
1682 .process_sliding_sync(
1683 &response,
1684 &RequestedRequiredStates::default(),
1685 &client.state_store_lock().lock().await,
1686 )
1687 .await
1688 .expect("Failed to process sync");
1689
1690 assert_matches!(
1693 room_info_notable_update.recv().await,
1694 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1695 assert_eq!(received_room_id, room_id);
1696 assert!(reasons.contains(RoomInfoNotableUpdateReasons::HEROES));
1697 }
1698 );
1699 assert!(room_info_notable_update.is_empty());
1700
1701 assert_ready!(room_info_subscriber);
1704 assert_pending!(room_info_subscriber);
1705
1706 let heroes = room.heroes().await;
1708 assert_eq!(heroes.len(), 1);
1709 assert_eq!(heroes[0].user_id, alice);
1710 let status = heroes[0].status.as_ref().expect("Alice's status should be set");
1711 assert_eq!(status.text, "Away");
1712 assert_eq!(status.emoji, "🌴");
1713 }
1714
1715 #[async_test]
1716 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1717 let client = logged_in_base_client(None).await;
1719 let room_id = room_id!("!r:e.uk");
1720
1721 let room = assign!(http::response::Room::new(), {
1723 bump_stamp: Some(42u32.into()),
1724 });
1725 let response = response_with_room(room_id, room);
1726 client
1727 .process_sliding_sync(
1728 &response,
1729 &RequestedRequiredStates::default(),
1730 &client.state_store_lock().lock().await,
1731 )
1732 .await
1733 .expect("Failed to process sync");
1734
1735 let client_room = client.get_room(room_id).expect("No room found");
1737 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1738 }
1739
1740 #[async_test]
1741 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1742 let client = logged_in_base_client(None).await;
1744 let room_id = room_id!("!r:e.uk");
1745
1746 {
1747 let room = assign!(http::response::Room::new(), {
1749 bump_stamp: Some(42u32.into()),
1750 });
1751 let response = response_with_room(room_id, room);
1752 client
1753 .process_sliding_sync(
1754 &response,
1755 &RequestedRequiredStates::default(),
1756 &client.state_store_lock().lock().await,
1757 )
1758 .await
1759 .expect("Failed to process sync");
1760
1761 let client_room = client.get_room(room_id).expect("No room found");
1763 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1764 }
1765
1766 {
1767 let room = assign!(http::response::Room::new(), {
1769 bump_stamp: None,
1770 });
1771 let response = response_with_room(room_id, room);
1772 client
1773 .process_sliding_sync(
1774 &response,
1775 &RequestedRequiredStates::default(),
1776 &client.state_store_lock().lock().await,
1777 )
1778 .await
1779 .expect("Failed to process sync");
1780
1781 let client_room = client.get_room(room_id).expect("No room found");
1783 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1784 }
1785
1786 {
1787 let room = assign!(http::response::Room::new(), {
1790 bump_stamp: Some(153u32.into()),
1791 });
1792 let response = response_with_room(room_id, room);
1793 client
1794 .process_sliding_sync(
1795 &response,
1796 &RequestedRequiredStates::default(),
1797 &client.state_store_lock().lock().await,
1798 )
1799 .await
1800 .expect("Failed to process sync");
1801
1802 let client_room = client.get_room(room_id).expect("No room found");
1804 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1805 }
1806 }
1807
1808 #[async_test]
1809 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1810 let client = logged_in_base_client(None).await;
1812 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1813 let room_id = room_id!("!r:e.uk");
1814
1815 let room = assign!(http::response::Room::new(), {
1817 bump_stamp: Some(42u32.into()),
1818 });
1819 let response = response_with_room(room_id, room);
1820 client
1821 .process_sliding_sync(
1822 &response,
1823 &RequestedRequiredStates::default(),
1824 &client.state_store_lock().lock().await,
1825 )
1826 .await
1827 .expect("Failed to process sync");
1828
1829 assert_matches!(
1832 room_info_notable_update_stream.recv().await,
1833 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1834 assert_eq!(received_room_id, room_id);
1835 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1836 }
1837 );
1838 assert_matches!(
1839 room_info_notable_update_stream.recv().await,
1840 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1841 assert_eq!(received_room_id, room_id);
1842 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1843 }
1844 );
1845 assert!(room_info_notable_update_stream.is_empty());
1846
1847 let room = assign!(http::response::Room::new(), {
1849 bump_stamp: Some(43u32.into()),
1850 });
1851 let response = response_with_room(room_id, room);
1852 client
1853 .process_sliding_sync(
1854 &response,
1855 &RequestedRequiredStates::default(),
1856 &client.state_store_lock().lock().await,
1857 )
1858 .await
1859 .expect("Failed to process sync");
1860
1861 assert_matches!(
1863 room_info_notable_update_stream.recv().await,
1864 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1865 assert_eq!(received_room_id, room_id);
1866 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1867 }
1868 );
1869 assert!(room_info_notable_update_stream.is_empty());
1870 }
1871
1872 #[async_test]
1873 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1874 let client = logged_in_base_client(None).await;
1876 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1877
1878 let room_id = room_id!("!r:e.uk");
1880 let room = http::response::Room::new();
1881 let response = response_with_room(room_id, room);
1882 client
1883 .process_sliding_sync(
1884 &response,
1885 &RequestedRequiredStates::default(),
1886 &client.state_store_lock().lock().await,
1887 )
1888 .await
1889 .expect("Failed to process sync");
1890
1891 assert_matches!(
1893 room_info_notable_update_stream.recv().await,
1894 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1895 assert_eq!(received_room_id, room_id);
1896 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1897 }
1898 );
1899 assert_matches!(
1900 room_info_notable_update_stream.recv().await,
1901 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1902 assert_eq!(received_room_id, room_id);
1903 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1904 }
1905 );
1906
1907 let room_id = room_id!("!r:e.uk");
1909 let events = vec![
1910 Raw::from_json_string(
1911 json!({
1912 "type": "m.room.member",
1913 "event_id": "$3",
1914 "content": { "membership": "join" },
1915 "sender": "@u:h.uk",
1916 "origin_server_ts": 12344445,
1917 "state_key": "@u:e.uk",
1918 })
1919 .to_string(),
1920 )
1921 .unwrap(),
1922 ];
1923 let room = assign!(http::response::Room::new(), {
1924 required_state: events,
1925 });
1926 let response = response_with_room(room_id, room);
1927 client
1928 .process_sliding_sync(
1929 &response,
1930 &RequestedRequiredStates::default(),
1931 &client.state_store_lock().lock().await,
1932 )
1933 .await
1934 .expect("Failed to process sync");
1935
1936 assert_matches!(
1938 room_info_notable_update_stream.recv().await,
1939 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1940 assert_eq!(received_room_id, room_id);
1941 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1942 }
1943 );
1944 assert!(room_info_notable_update_stream.is_empty());
1945
1946 let events = vec![
1947 Raw::from_json_string(
1948 json!({
1949 "type": "m.room.member",
1950 "event_id": "$3",
1951 "content": { "membership": "leave" },
1952 "sender": "@u:h.uk",
1953 "origin_server_ts": 12344445,
1954 "state_key": "@u:e.uk",
1955 })
1956 .to_string(),
1957 )
1958 .unwrap(),
1959 ];
1960 let room = assign!(http::response::Room::new(), {
1961 required_state: events,
1962 });
1963 let response = response_with_room(room_id, room);
1964 client
1965 .process_sliding_sync(
1966 &response,
1967 &RequestedRequiredStates::default(),
1968 &client.state_store_lock().lock().await,
1969 )
1970 .await
1971 .expect("Failed to process sync");
1972
1973 assert_matches!(
1975 room_info_notable_update_stream.recv().await,
1976 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1977 assert_eq!(received_room_id, room_id);
1978 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1979 }
1980 );
1981 assert!(room_info_notable_update_stream.is_empty());
1982 }
1983
1984 #[async_test]
1985 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1986 let client = logged_in_base_client(None).await;
1988 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1989
1990 let room_id = room_id!("!r:e.uk");
1992 let room = http::response::Room::new();
1993 let response = response_with_room(room_id, room);
1994 client
1995 .process_sliding_sync(
1996 &response,
1997 &RequestedRequiredStates::default(),
1998 &client.state_store_lock().lock().await,
1999 )
2000 .await
2001 .expect("Failed to process sync");
2002
2003 assert_matches!(
2005 room_info_notable_update_stream.recv().await,
2006 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2007 assert_eq!(received_room_id, room_id);
2008 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2009 }
2010 );
2011 assert_matches!(
2012 room_info_notable_update_stream.recv().await,
2013 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2014 assert_eq!(received_room_id, room_id);
2015 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2016 }
2017 );
2018 assert!(room_info_notable_update_stream.is_empty());
2019
2020 let room_id = room_id!("!r:e.uk");
2023 let room_account_data_events = vec![
2024 Raw::from_json_string(
2025 json!({
2026 "type": "m.marked_unread",
2027 "event_id": "$1",
2028 "content": { "unread": true },
2029 "sender": client.session_meta().unwrap().user_id,
2030 "origin_server_ts": 12344445,
2031 })
2032 .to_string(),
2033 )
2034 .unwrap(),
2035 ];
2036 let mut response = response_with_room(room_id, http::response::Room::new());
2037 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2038
2039 client
2040 .process_sliding_sync(
2041 &response,
2042 &RequestedRequiredStates::default(),
2043 &client.state_store_lock().lock().await,
2044 )
2045 .await
2046 .expect("Failed to process sync");
2047
2048 assert_matches!(
2050 room_info_notable_update_stream.recv().await,
2051 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2052 assert_eq!(received_room_id, room_id);
2053 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2054 }
2055 );
2056
2057 client
2059 .process_sliding_sync(
2060 &response,
2061 &RequestedRequiredStates::default(),
2062 &client.state_store_lock().lock().await,
2063 )
2064 .await
2065 .expect("Failed to process sync");
2066
2067 assert_matches!(
2068 room_info_notable_update_stream.recv().await,
2069 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2070 assert_eq!(received_room_id, room_id);
2071 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2072 }
2073 );
2074 assert!(room_info_notable_update_stream.is_empty());
2075
2076 let room_account_data_events = vec![
2078 Raw::from_json_string(
2079 json!({
2080 "type": "m.marked_unread",
2081 "event_id": "$1",
2082 "content": { "unread": false },
2083 "sender": client.session_meta().unwrap().user_id,
2084 "origin_server_ts": 12344445,
2085 })
2086 .to_string(),
2087 )
2088 .unwrap(),
2089 ];
2090 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2091 client
2092 .process_sliding_sync(
2093 &response,
2094 &RequestedRequiredStates::default(),
2095 &client.state_store_lock().lock().await,
2096 )
2097 .await
2098 .expect("Failed to process sync");
2099
2100 assert_matches!(
2101 room_info_notable_update_stream.recv().await,
2102 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2103 assert_eq!(received_room_id, room_id);
2104 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2105 }
2106 );
2107 assert!(room_info_notable_update_stream.is_empty());
2108 }
2109
2110 #[async_test]
2111 async fn test_empty_room_account_data_does_not_create_a_room_update() {
2112 let client = logged_in_base_client(None).await;
2113
2114 let room_id_a = room_id!("!a:e.uk");
2115 let room_id_b = room_id!("!b:e.uk");
2116 let user_id = client.session_meta().unwrap().user_id.clone();
2117
2118 let mut response = http::Response::new("0".to_owned());
2119 for room_id in [room_id_a, room_id_b] {
2120 let mut room = http::response::Room::new();
2121 set_room_joined(&mut room, &user_id);
2122 response.rooms.insert(room_id.to_owned(), room);
2123 }
2124 client
2125 .process_sliding_sync(
2126 &response,
2127 &RequestedRequiredStates::default(),
2128 &client.state_store_lock().lock().await,
2129 )
2130 .await
2131 .expect("Failed to process sync");
2132
2133 let mut response = response_with_room(room_id_a, http::response::Room::new());
2134 response.extensions.account_data.rooms.insert(room_id_b.to_owned(), vec![]);
2135
2136 let sync_response = 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!(sync_response.rooms.joined.contains_key(room_id_a));
2146 assert!(!sync_response.rooms.joined.contains_key(room_id_b));
2147 assert!(sync_response.rooms.left.is_empty());
2148 }
2149
2150 #[async_test]
2151 async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
2152 let client = logged_in_base_client(None).await;
2154 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2155
2156 let room_id = room_id!("!r:e.uk");
2158 let room = http::response::Room::new();
2159 let response = response_with_room(room_id, room);
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::NONE), "{received_reasons:?}");
2175 }
2176 );
2177 assert_matches!(
2178 room_info_notable_update_stream.recv().await,
2179 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2180 assert_eq!(received_room_id, room_id);
2181 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2182 }
2183 );
2184 assert!(room_info_notable_update_stream.is_empty());
2185
2186 let room_account_data_events = vec![
2189 Raw::from_json_string(
2190 json!({
2191 "type": "m.fully_read",
2192 "content": { "event_id": "$first" },
2193 })
2194 .to_string(),
2195 )
2196 .unwrap(),
2197 ];
2198 let mut response = response_with_room(room_id, http::response::Room::new());
2199 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2200
2201 client
2202 .process_sliding_sync(
2203 &response,
2204 &RequestedRequiredStates::default(),
2205 &client.state_store_lock().lock().await,
2206 )
2207 .await
2208 .expect("Failed to process sync");
2209
2210 assert_matches!(
2212 room_info_notable_update_stream.recv().await,
2213 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2214 assert_eq!(received_room_id, room_id);
2215 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2216 }
2217 );
2218
2219 let room = client.get_room(room_id).expect("room should exist");
2220 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2221
2222 client
2224 .process_sliding_sync(
2225 &response,
2226 &RequestedRequiredStates::default(),
2227 &client.state_store_lock().lock().await,
2228 )
2229 .await
2230 .expect("Failed to process sync");
2231
2232 assert_matches!(
2233 room_info_notable_update_stream.recv().await,
2234 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2235 assert_eq!(received_room_id, room_id);
2236 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2237 }
2238 );
2239 assert!(room_info_notable_update_stream.is_empty());
2240
2241 let room_account_data_events = vec![
2243 Raw::from_json_string(
2244 json!({
2245 "type": "m.fully_read",
2246 "content": { "event_id": "$second" },
2247 })
2248 .to_string(),
2249 )
2250 .unwrap(),
2251 ];
2252 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2253 client
2254 .process_sliding_sync(
2255 &response,
2256 &RequestedRequiredStates::default(),
2257 &client.state_store_lock().lock().await,
2258 )
2259 .await
2260 .expect("Failed to process sync");
2261
2262 assert_matches!(
2263 room_info_notable_update_stream.recv().await,
2264 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2265 assert_eq!(received_room_id, room_id);
2266 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2267 }
2268 );
2269 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2270 assert!(room_info_notable_update_stream.is_empty());
2271 }
2272
2273 #[async_test]
2274 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2275 let client = logged_in_base_client(None).await;
2277 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2278
2279 let room_id = room_id!("!r:e.uk");
2281 let room = http::response::Room::new();
2282 let response = response_with_room(room_id, room);
2283 client
2284 .process_sliding_sync(
2285 &response,
2286 &RequestedRequiredStates::default(),
2287 &client.state_store_lock().lock().await,
2288 )
2289 .await
2290 .expect("Failed to process sync");
2291
2292 assert_matches!(
2294 room_info_notable_update_stream.recv().await,
2295 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2296 assert_eq!(received_room_id, room_id);
2297 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2298 }
2299 );
2300 assert_matches!(
2301 room_info_notable_update_stream.recv().await,
2302 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2303 assert_eq!(received_room_id, room_id);
2304 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2305 }
2306 );
2307 assert!(room_info_notable_update_stream.is_empty());
2308
2309 let room_id = room_id!("!r:e.uk");
2312 let unstable_room_account_data_events = vec![
2313 Raw::from_json_string(
2314 json!({
2315 "type": "com.famedly.marked_unread",
2316 "event_id": "$1",
2317 "content": { "unread": true },
2318 "sender": client.session_meta().unwrap().user_id,
2319 "origin_server_ts": 12344445,
2320 })
2321 .to_string(),
2322 )
2323 .unwrap(),
2324 ];
2325 let mut response = response_with_room(room_id, http::response::Room::new());
2326 response
2327 .extensions
2328 .account_data
2329 .rooms
2330 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2331
2332 client
2333 .process_sliding_sync(
2334 &response,
2335 &RequestedRequiredStates::default(),
2336 &client.state_store_lock().lock().await,
2337 )
2338 .await
2339 .expect("Failed to process sync");
2340
2341 assert_matches!(
2343 room_info_notable_update_stream.recv().await,
2344 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2345 assert_eq!(received_room_id, room_id);
2346 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2347 }
2348 );
2349 assert!(room_info_notable_update_stream.is_empty());
2350
2351 let stable_room_account_data_events = vec![
2353 Raw::from_json_string(
2354 json!({
2355 "type": "m.marked_unread",
2356 "event_id": "$1",
2357 "content": { "unread": false },
2358 "sender": client.session_meta().unwrap().user_id,
2359 "origin_server_ts": 12344445,
2360 })
2361 .to_string(),
2362 )
2363 .unwrap(),
2364 ];
2365 response
2366 .extensions
2367 .account_data
2368 .rooms
2369 .insert(room_id.to_owned(), stable_room_account_data_events);
2370 client
2371 .process_sliding_sync(
2372 &response,
2373 &RequestedRequiredStates::default(),
2374 &client.state_store_lock().lock().await,
2375 )
2376 .await
2377 .expect("Failed to process sync");
2378
2379 assert_matches!(
2381 room_info_notable_update_stream.recv().await,
2382 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2383 assert_eq!(received_room_id, room_id);
2384 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2385 }
2386 );
2387 assert!(room_info_notable_update_stream.is_empty());
2388
2389 response
2392 .extensions
2393 .account_data
2394 .rooms
2395 .insert(room_id.to_owned(), unstable_room_account_data_events);
2396 client
2397 .process_sliding_sync(
2398 &response,
2399 &RequestedRequiredStates::default(),
2400 &client.state_store_lock().lock().await,
2401 )
2402 .await
2403 .expect("Failed to process sync");
2404
2405 assert_matches!(
2407 room_info_notable_update_stream.recv().await,
2408 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2409 assert_eq!(received_room_id, room_id);
2410 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2411 }
2412 );
2413 assert!(room_info_notable_update_stream.is_empty());
2414
2415 let stable_room_account_data_events = vec![
2418 Raw::from_json_string(
2419 json!({
2420 "type": "m.marked_unread",
2421 "event_id": "$3",
2422 "content": { "unread": true },
2423 "sender": client.session_meta().unwrap().user_id,
2424 "origin_server_ts": 12344445,
2425 })
2426 .to_string(),
2427 )
2428 .unwrap(),
2429 ];
2430 response
2431 .extensions
2432 .account_data
2433 .rooms
2434 .insert(room_id.to_owned(), stable_room_account_data_events);
2435 client
2436 .process_sliding_sync(
2437 &response,
2438 &RequestedRequiredStates::default(),
2439 &client.state_store_lock().lock().await,
2440 )
2441 .await
2442 .expect("Failed to process sync");
2443
2444 assert_matches!(
2446 room_info_notable_update_stream.recv().await,
2447 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2448 assert_eq!(received_room_id, room_id);
2449 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2450 }
2451 );
2452 assert!(room_info_notable_update_stream.is_empty());
2453 }
2454
2455 #[async_test]
2456 async fn test_pinned_events_are_updated_on_sync() {
2457 let user_a_id = user_id!("@a:e.uk");
2458 let client = logged_in_base_client(Some(user_a_id)).await;
2459 let room_id = room_id!("!r:e.uk");
2460 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2461
2462 let mut room_response = http::response::Room::new();
2464 set_room_joined(&mut room_response, user_a_id);
2465 let response = response_with_room(room_id, room_response);
2466 client
2467 .process_sliding_sync(
2468 &response,
2469 &RequestedRequiredStates::default(),
2470 &client.state_store_lock().lock().await,
2471 )
2472 .await
2473 .expect("Failed to process sync");
2474
2475 let room = client.get_room(room_id).unwrap();
2477 let pinned_event_ids = room.pinned_event_ids();
2478 assert_matches!(pinned_event_ids, None);
2479
2480 let mut room_response = http::response::Room::new();
2482 room_response.required_state.push(make_state_event(
2483 user_a_id,
2484 "",
2485 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2486 None,
2487 ));
2488 let response = response_with_room(room_id, room_response);
2489 client
2490 .process_sliding_sync(
2491 &response,
2492 &RequestedRequiredStates::default(),
2493 &client.state_store_lock().lock().await,
2494 )
2495 .await
2496 .expect("Failed to process sync");
2497
2498 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2499 assert_eq!(pinned_event_ids.len(), 1);
2500 assert_eq!(pinned_event_ids[0], pinned_event_id);
2501
2502 let mut room_response = http::response::Room::new();
2504 room_response.required_state.push(make_state_event(
2505 user_a_id,
2506 "",
2507 RoomPinnedEventsEventContent::new(Vec::new()),
2508 None,
2509 ));
2510 let response = response_with_room(room_id, room_response);
2511 client
2512 .process_sliding_sync(
2513 &response,
2514 &RequestedRequiredStates::default(),
2515 &client.state_store_lock().lock().await,
2516 )
2517 .await
2518 .expect("Failed to process sync");
2519 let pinned_event_ids = room.pinned_event_ids().unwrap();
2520 assert!(pinned_event_ids.is_empty());
2521 }
2522
2523 #[async_test]
2524 async fn test_dms_are_processed_in_any_sync_response() {
2525 let current_user_id = user_id!("@current:e.uk");
2526 let client = logged_in_base_client(Some(current_user_id)).await;
2527 let user_a_id = user_id!("@a:e.uk");
2528 let user_b_id = user_id!("@b:e.uk");
2529 let room_id_1 = room_id!("!r:e.uk");
2530 let room_id_2 = room_id!("!s:e.uk");
2531
2532 let mut room_response = http::response::Room::new();
2533 set_room_joined(&mut room_response, user_a_id);
2534 let mut response = response_with_room(room_id_1, room_response);
2535 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2536 BTreeMap::new();
2537 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2538 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2539 response
2540 .extensions
2541 .account_data
2542 .global
2543 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2544 client
2545 .process_sliding_sync(
2546 &response,
2547 &RequestedRequiredStates::default(),
2548 &client.state_store_lock().lock().await,
2549 )
2550 .await
2551 .expect("Failed to process sync");
2552
2553 let room_1 = client.get_room(room_id_1).unwrap();
2554 assert!(room_1.is_direct().await.unwrap());
2555
2556 let mut room_response = http::response::Room::new();
2558 set_room_joined(&mut room_response, user_b_id);
2559 let response = response_with_room(room_id_2, room_response);
2560 client
2561 .process_sliding_sync(
2562 &response,
2563 &RequestedRequiredStates::default(),
2564 &client.state_store_lock().lock().await,
2565 )
2566 .await
2567 .expect("Failed to process sync");
2568
2569 let room_2 = client.get_room(room_id_2).unwrap();
2570 assert!(room_2.is_direct().await.unwrap());
2571 }
2572
2573 #[async_test]
2574 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2575 let user_id = user_id!("@raclette:patate");
2576 let client = logged_in_base_client(Some(user_id)).await;
2577 let room_id_0 = room_id!("!r0");
2578 let room_id_1 = room_id!("!r1");
2579 let room_id_2 = room_id!("!r2");
2580
2581 let requested_required_states = RequestedRequiredStates::from(&{
2598 let mut request = http::Request::new();
2599
2600 request.room_subscriptions.insert(room_id_0.to_owned(), {
2601 let mut room_subscription = http::request::RoomSubscription::default();
2602
2603 room_subscription
2604 .required_state
2605 .push((StateEventType::RoomEncryption, "".to_owned()));
2606
2607 room_subscription
2608 });
2609
2610 request
2611 });
2612
2613 let mut response = http::Response::new("0".to_owned());
2614
2615 {
2619 let not_encrypted_room = http::response::Room::new();
2620 let mut encrypted_room = http::response::Room::new();
2621 set_room_is_encrypted(&mut encrypted_room, user_id);
2622
2623 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2624 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2625 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2626 }
2627
2628 client
2629 .process_sliding_sync(
2630 &response,
2631 &requested_required_states,
2632 &client.state_store_lock().lock().await,
2633 )
2634 .await
2635 .expect("Failed to process sync");
2636
2637 assert_matches!(
2639 client.get_room(room_id_0).unwrap().encryption_state(),
2640 EncryptionState::Encrypted
2641 );
2642 assert_matches!(
2643 client.get_room(room_id_1).unwrap().encryption_state(),
2644 EncryptionState::Encrypted
2645 );
2646 assert_matches!(
2648 client.get_room(room_id_2).unwrap().encryption_state(),
2649 EncryptionState::NotEncrypted
2650 )
2651 }
2652
2653 #[async_test]
2654 async fn test_room_encryption_state_is_unknown() {
2655 let user_id = user_id!("@raclette:patate");
2656 let client = logged_in_base_client(Some(user_id)).await;
2657 let room_id_0 = room_id!("!r0");
2658 let room_id_1 = room_id!("!r1");
2659
2660 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2673
2674 let mut response = http::Response::new("0".to_owned());
2675
2676 {
2678 let not_encrypted_room = http::response::Room::new();
2679 let mut encrypted_room = http::response::Room::new();
2680 set_room_is_encrypted(&mut encrypted_room, user_id);
2681
2682 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2683 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2684 }
2685
2686 client
2687 .process_sliding_sync(
2688 &response,
2689 &requested_required_states,
2690 &client.state_store_lock().lock().await,
2691 )
2692 .await
2693 .expect("Failed to process sync");
2694
2695 assert_matches!(
2698 client.get_room(room_id_0).unwrap().encryption_state(),
2699 EncryptionState::Encrypted
2700 );
2701 assert_matches!(
2704 client.get_room(room_id_1).unwrap().encryption_state(),
2705 EncryptionState::Unknown
2706 );
2707 }
2708
2709 async fn membership(
2710 client: &BaseClient,
2711 room_id: &RoomId,
2712 user_id: &UserId,
2713 ) -> MembershipState {
2714 let room = client.get_room(room_id).expect("Room not found!");
2715 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2716 member.membership().clone()
2717 }
2718
2719 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2720 let room = client.get_room(room_id).expect("Room not found!");
2721 room.direct_targets()
2722 }
2723
2724 async fn create_dm(
2727 client: &BaseClient,
2728 room_id: &RoomId,
2729 my_id: &UserId,
2730 their_id: &UserId,
2731 other_state: MembershipState,
2732 ) {
2733 let mut room = http::response::Room::new();
2734 set_room_joined(&mut room, my_id);
2735
2736 match other_state {
2737 MembershipState::Join => {
2738 room.joined_count = Some(uint!(2));
2739 room.invited_count = None;
2740 }
2741
2742 MembershipState::Invite => {
2743 room.joined_count = Some(uint!(1));
2744 room.invited_count = Some(uint!(1));
2745 }
2746
2747 _ => {
2748 room.joined_count = Some(uint!(1));
2749 room.invited_count = None;
2750 }
2751 }
2752
2753 room.required_state.push(make_membership_event(their_id, other_state));
2754
2755 let mut response = response_with_room(room_id, room);
2756 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2757 client
2758 .process_sliding_sync(
2759 &response,
2760 &RequestedRequiredStates::default(),
2761 &client.state_store_lock().lock().await,
2762 )
2763 .await
2764 .expect("Failed to process sync");
2765 }
2766
2767 async fn update_room_membership(
2769 client: &BaseClient,
2770 room_id: &RoomId,
2771 user_id: &UserId,
2772 new_state: MembershipState,
2773 ) {
2774 let mut room = http::response::Room::new();
2775 room.required_state.push(make_membership_event(user_id, new_state));
2776 let response = response_with_room(room_id, room);
2777 client
2778 .process_sliding_sync(
2779 &response,
2780 &RequestedRequiredStates::default(),
2781 &client.state_store_lock().lock().await,
2782 )
2783 .await
2784 .expect("Failed to process sync");
2785 }
2786
2787 fn set_direct_with(
2788 response: &mut http::Response,
2789 user_id: OwnedUserId,
2790 room_ids: Vec<OwnedRoomId>,
2791 ) {
2792 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2793 BTreeMap::new();
2794 direct_content.insert(user_id.into(), room_ids);
2795 response
2796 .extensions
2797 .account_data
2798 .global
2799 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2800 }
2801
2802 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2803 let mut response = http::Response::new("5".to_owned());
2804 response.rooms.insert(room_id.to_owned(), room);
2805 response
2806 }
2807
2808 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2809 let mut room = http::response::Room::new();
2810
2811 let mut avatar_event_content = RoomAvatarEventContent::new();
2812 avatar_event_content.url = Some(avatar_uri.to_owned());
2813
2814 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2815
2816 room
2817 }
2818
2819 fn room_with_canonical_alias(
2820 room_alias_id: &RoomAliasId,
2821 user_id: &UserId,
2822 ) -> http::response::Room {
2823 let mut room = http::response::Room::new();
2824
2825 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2826 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2827
2828 room.required_state.push(make_state_event(
2829 user_id,
2830 "",
2831 canonical_alias_event_content,
2832 None,
2833 ));
2834
2835 room
2836 }
2837
2838 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2839 let mut room = http::response::Room::new();
2840
2841 let name_event_content = RoomNameEventContent::new(name.to_owned());
2842
2843 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2844
2845 room
2846 }
2847
2848 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2849 room.required_state.push(make_state_event(
2850 sender,
2851 "",
2852 RoomNameEventContent::new(name),
2853 None,
2854 ));
2855 }
2856
2857 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2858 let evt = Raw::new(&json!({
2862 "type": "m.room.member",
2863 "sender": inviter,
2864 "content": {
2865 "is_direct": true,
2866 "membership": "invite",
2867 },
2868 "state_key": invitee,
2869 }))
2870 .expect("Failed to make raw event")
2871 .cast_unchecked();
2872
2873 room.invite_state = Some(vec![evt]);
2874
2875 room.required_state.push(make_state_event(
2878 inviter,
2879 invitee.as_str(),
2880 RoomMemberEventContent::new(MembershipState::Invite),
2881 None,
2882 ));
2883 }
2884
2885 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2886 let evt = Raw::new(&json!({
2890 "type": "m.room.member",
2891 "sender": knocker,
2892 "content": {
2893 "is_direct": true,
2894 "membership": "knock",
2895 },
2896 "state_key": knocker,
2897 }))
2898 .expect("Failed to make raw event")
2899 .cast_unchecked();
2900
2901 room.invite_state = Some(vec![evt]);
2902 }
2903
2904 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2905 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2906 }
2907
2908 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2909 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2910 }
2911
2912 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2913 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2914 }
2915
2916 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2917 room.required_state.push(make_encryption_event(user_id));
2918 }
2919
2920 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2921 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2922 }
2923
2924 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2925 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2926 }
2927
2928 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2929 Raw::new(&json!({
2930 "type": content.event_type(),
2931 "content": content,
2932 }))
2933 .expect("Failed to create account data event")
2934 .cast_unchecked()
2935 }
2936
2937 fn make_state_event<C: StateEventContent, E>(
2938 sender: &UserId,
2939 state_key: &str,
2940 content: C,
2941 prev_content: Option<C>,
2942 ) -> Raw<E> {
2943 let unsigned = if let Some(prev_content) = prev_content {
2944 json!({ "prev_content": prev_content })
2945 } else {
2946 json!({})
2947 };
2948
2949 Raw::new(&json!({
2950 "type": content.event_type(),
2951 "state_key": state_key,
2952 "content": content,
2953 "event_id": event_id!("$evt"),
2954 "sender": sender,
2955 "origin_server_ts": 10,
2956 "unsigned": unsigned,
2957 }))
2958 .expect("Failed to create state event")
2959 .cast_unchecked()
2960 }
2961
2962 fn make_profile_update(field: ProfileFieldName, value: serde_json::Value) -> UserProfileUpdate {
2963 let mut changes = UserProfileChanges::new();
2964 changes.updated.insert(field, value);
2965 UserProfileUpdate::Updated(changes)
2966 }
2967}