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.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 let mut context = processors::Context::default();
223
224 processors::room::display_name::update_for_rooms(
227 &mut context,
228 &room_updates,
229 &self.state_store,
230 )
231 .await;
232
233 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
235
236 Ok(SyncResponse {
237 rooms: room_updates,
238 notifications,
239 presence: Default::default(),
240 account_data: extensions.account_data.global.clone(),
241 to_device: Default::default(),
242 })
243 }
244
245 #[doc(hidden)]
248 pub async fn process_sliding_sync_receipts_extension_for_room(
249 &self,
250 room_id: &OwnedRoomId,
251 response: &http::Response,
252 state_store_guard: &MutexGuard<'_, ()>,
253 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
254 let mut context = processors::Context::default();
255
256 let mut save_context = false;
257
258 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
260 response.extensions.receipts.rooms.get(room_id)
261 {
262 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
263 &mut context,
264 room_id,
265 receipt_ephemeral_event,
266 );
267 save_context = true;
268 Some(receipt_ephemeral_event.clone())
269 } else {
270 None
271 };
272
273 if save_context {
275 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
276 }
277
278 Ok(receipt_ephemeral_event)
279 }
280}
281
282#[cfg(all(test, not(target_family = "wasm")))]
283mod tests {
284 use std::collections::{BTreeMap, HashSet};
285
286 use assert_matches::assert_matches;
287 use matrix_sdk_test::async_test;
288 use ruma::{
289 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
290 api::client::sync::sync_events::UnreadNotificationsCount,
291 assign, event_id,
292 events::{
293 GlobalAccountDataEventContent, StateEventContent, StateEventType,
294 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
295 room::{
296 avatar::RoomAvatarEventContent,
297 canonical_alias::RoomCanonicalAliasEventContent,
298 encryption::RoomEncryptionEventContent,
299 member::{MembershipState, RoomMemberEventContent},
300 name::RoomNameEventContent,
301 pinned_events::RoomPinnedEventsEventContent,
302 },
303 },
304 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
305 profile::UserProfileUpdate,
306 room_alias_id, room_id,
307 serde::Raw,
308 uint, user_id,
309 };
310 use serde_json::json;
311
312 use super::http;
313 use crate::{
314 BaseClient, DmRoomDefinition, EncryptionState, RequestedRequiredStates,
315 RoomInfoNotableUpdate, RoomState, SessionMeta,
316 client::ThreadingSupport,
317 room::{RoomHero, RoomInfoNotableUpdateReasons},
318 store::{RoomLoadSettings, StoreConfig},
319 test_utils::logged_in_base_client,
320 };
321
322 #[async_test]
323 async fn test_invited_state_without_update_emits_invited_room() {
324 let client = logged_in_base_client(None).await;
325 let room_id = room_id!("!invite:e.uk");
326 let user_id = client.session_meta().unwrap().user_id.to_owned();
327
328 let mut room = http::response::Room::new();
329 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Invite));
330
331 let response = response_with_room(room_id, room);
332
333 let sync_resp = client
334 .process_sliding_sync(
335 &response,
336 &RequestedRequiredStates::default(),
337 &client.state_store_lock().lock().await,
338 )
339 .await
340 .unwrap();
341
342 assert!(sync_resp.rooms.invited.contains_key(room_id));
343 }
344
345 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
346 use ruma::events::AnyStrippedStateEvent;
347
348 fn invite_state_for(
349 user_id: &UserId,
350 membership: MembershipState,
351 ) -> Vec<Raw<AnyStrippedStateEvent>> {
352 let content = RoomMemberEventContent::new(membership);
353
354 let raw: Raw<AnyStrippedStateEvent> = Raw::from_json_string(
355 serde_json::json!({
356 "type": "m.room.member",
357 "state_key": user_id,
358 "content": content,
359 })
360 .to_string(),
361 )
362 .unwrap();
363
364 vec![raw]
365 }
366
367 #[async_test]
368 async fn test_knocked_state_emits_invited_room() {
369 let client = logged_in_base_client(None).await;
370 let room_id = room_id!("!knock:e.uk");
371 let user_id = client.session_meta().unwrap().user_id.to_owned();
372
373 let mut room = http::response::Room::new();
374 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Knock));
375
376 let response = response_with_room(room_id, room);
377
378 let sync_resp = client
379 .process_sliding_sync(
380 &response,
381 &RequestedRequiredStates::default(),
382 &client.state_store_lock().lock().await,
383 )
384 .await
385 .unwrap();
386
387 assert!(sync_resp.rooms.invited.contains_key(room_id));
389 }
390
391 #[async_test]
392 async fn test_notification_count_set() {
393 let client = logged_in_base_client(None).await;
394
395 let mut response = http::Response::new("42".to_owned());
396 let room_id = room_id!("!room:example.org");
397 let count = assign!(UnreadNotificationsCount::default(), {
398 highlight_count: Some(uint!(13)),
399 notification_count: Some(uint!(37)),
400 });
401
402 response.rooms.insert(
403 room_id.to_owned(),
404 assign!(http::response::Room::new(), {
405 unread_notifications: count.clone()
406 }),
407 );
408
409 let sync_response = client
410 .process_sliding_sync(
411 &response,
412 &RequestedRequiredStates::default(),
413 &client.state_store_lock().lock().await,
414 )
415 .await
416 .expect("Failed to process sync");
417
418 let room = sync_response.rooms.joined.get(room_id).unwrap();
420 assert_eq!(room.unread_notifications, count.clone().into());
421
422 let room = client.get_room(room_id).expect("found room");
424 assert_eq!(room.unread_notification_counts(), count.into());
425 }
426
427 #[async_test]
428 async fn test_can_process_empty_sliding_sync_response() {
429 let client = logged_in_base_client(None).await;
430 let empty_response = http::Response::new("5".to_owned());
431 client
432 .process_sliding_sync(
433 &empty_response,
434 &RequestedRequiredStates::default(),
435 &client.state_store_lock().lock().await,
436 )
437 .await
438 .expect("Failed to process sync");
439 }
440
441 #[async_test]
442 async fn test_profiles_extension_is_persisted_from_sliding_sync() {
443 let client = logged_in_base_client(None).await;
444
445 let alice = user_id!("@alice:e.uk");
446 let bob = user_id!("@bob:e.uk");
447
448 let mut response = http::Response::new("0".to_owned());
451 response.extensions.profiles.insert(
452 alice.to_owned(),
453 UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Alice"))]),
454 );
455 response.extensions.profiles.insert(
456 bob.to_owned(),
457 UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Bob"))]),
458 );
459
460 client
462 .process_sliding_sync(
463 &response,
464 &RequestedRequiredStates::default(),
465 &client.state_store_lock().lock().await,
466 )
467 .await
468 .expect("Failed to process sync");
469
470 let store = client.state_store();
472
473 let alice_profile = store
474 .get_global_profile(alice)
475 .await
476 .expect("Failed to read profile")
477 .expect("Alice's profile should be saved");
478 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
479 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice")));
480
481 let bob_profile = store
482 .get_global_profile(bob)
483 .await
484 .expect("Failed to read profile")
485 .expect("Bob's profile should be saved");
486 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
487 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
488
489 let mut response = http::Response::new("1".to_owned());
491 response.extensions.profiles.insert(
492 alice.to_owned(),
493 UserProfileUpdate::from_iter([("displayname".to_owned(), json!("Alice Updated"))]),
494 );
495
496 client
497 .process_sliding_sync(
498 &response,
499 &RequestedRequiredStates::default(),
500 &client.state_store_lock().lock().await,
501 )
502 .await
503 .expect("Failed to process sync");
504
505 let alice_profile = store
507 .get_global_profile(alice)
508 .await
509 .expect("Failed to read profile")
510 .expect("Alice's profile should be saved");
511 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
512 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice Updated")));
513
514 let bob_profile = store
515 .get_global_profile(bob)
516 .await
517 .expect("Failed to read profile")
518 .expect("Bob's profile should still be saved");
519 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
520 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
521 }
522
523 #[async_test]
524 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
525 let client = logged_in_base_client(None).await;
527 let room_id = room_id!("!r:e.uk");
528
529 let mut room = http::response::Room::new();
532 room.joined_count = Some(uint!(41));
533 let response = response_with_room(room_id, room);
534 let sync_resp = client
535 .process_sliding_sync(
536 &response,
537 &RequestedRequiredStates::default(),
538 &client.state_store_lock().lock().await,
539 )
540 .await
541 .expect("Failed to process sync");
542
543 let client_room = client.get_room(room_id).expect("No room found");
545 assert_eq!(client_room.room_id(), room_id);
546 assert_eq!(client_room.joined_members_count(), 41);
547 assert_eq!(client_room.state(), RoomState::Joined);
548
549 assert!(sync_resp.rooms.joined.contains_key(room_id));
551 assert!(!sync_resp.rooms.left.contains_key(room_id));
552 assert!(!sync_resp.rooms.invited.contains_key(room_id));
553 }
554
555 #[async_test]
556 async fn test_missing_room_name_event() {
557 let client = logged_in_base_client(None).await;
559 let room_id = room_id!("!r:e.uk");
560
561 let mut room = http::response::Room::new();
564 room.name = Some("little room".to_owned());
565 let response = response_with_room(room_id, room);
566 let sync_resp = client
567 .process_sliding_sync(
568 &response,
569 &RequestedRequiredStates::default(),
570 &client.state_store_lock().lock().await,
571 )
572 .await
573 .expect("Failed to process sync");
574
575 let client_room = client.get_room(room_id).expect("No room found");
577 assert!(client_room.name().is_none());
578 assert_eq!(
579 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
580 "Empty Room"
581 );
582 assert_eq!(client_room.state(), RoomState::Joined);
583
584 assert!(sync_resp.rooms.joined.contains_key(room_id));
586 assert!(!sync_resp.rooms.left.contains_key(room_id));
587 assert!(!sync_resp.rooms.invited.contains_key(room_id));
588 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
589 }
590
591 #[async_test]
592 async fn test_room_name_event() {
593 let client = logged_in_base_client(None).await;
595 let room_id = room_id!("!r:e.uk");
596
597 let mut room = http::response::Room::new();
600
601 room.name = Some("little room".to_owned());
602 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
603
604 let response = response_with_room(room_id, room);
605 client
606 .process_sliding_sync(
607 &response,
608 &RequestedRequiredStates::default(),
609 &client.state_store_lock().lock().await,
610 )
611 .await
612 .expect("Failed to process sync");
613
614 let client_room = client.get_room(room_id).expect("No room found");
616 assert_eq!(client_room.name().as_deref(), Some("The Name"));
617 assert_eq!(
618 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
619 "The Name"
620 );
621 }
622
623 #[async_test]
624 async fn test_missing_invited_room_name_event() {
625 let client = logged_in_base_client(None).await;
627 let room_id = room_id!("!r:e.uk");
628 let user_id = user_id!("@w:e.uk");
629 let inviter = user_id!("@john:mastodon.org");
630
631 let mut room = http::response::Room::new();
634 set_room_invited(&mut room, inviter, user_id);
635 room.name = Some("name from sliding sync response".to_owned());
636 let response = response_with_room(room_id, room);
637 let sync_resp = client
638 .process_sliding_sync(
639 &response,
640 &RequestedRequiredStates::default(),
641 &client.state_store_lock().lock().await,
642 )
643 .await
644 .expect("Failed to process sync");
645
646 let client_room = client.get_room(room_id).expect("No room found");
648 assert!(client_room.name().is_none());
649
650 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
652
653 assert_eq!(client_room.state(), RoomState::Invited);
654
655 assert!(!sync_resp.rooms.joined.contains_key(room_id));
657 assert!(!sync_resp.rooms.left.contains_key(room_id));
658 assert!(sync_resp.rooms.invited.contains_key(room_id));
659 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
660 }
661
662 #[async_test]
663 async fn test_invited_room_name_event() {
664 let client = logged_in_base_client(None).await;
666 let room_id = room_id!("!r:e.uk");
667 let user_id = user_id!("@w:e.uk");
668 let inviter = user_id!("@john:mastodon.org");
669
670 let mut room = http::response::Room::new();
673
674 set_room_invited(&mut room, inviter, user_id);
675
676 room.name = Some("name from sliding sync response".to_owned());
677 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
678
679 let response = response_with_room(room_id, room);
680 client
681 .process_sliding_sync(
682 &response,
683 &RequestedRequiredStates::default(),
684 &client.state_store_lock().lock().await,
685 )
686 .await
687 .expect("Failed to process sync");
688
689 let client_room = client.get_room(room_id).expect("No room found");
691 assert_eq!(client_room.name().as_deref(), Some("The Name"));
692 assert_eq!(
693 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
694 "The Name"
695 );
696 }
697
698 #[async_test]
699 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
700 let client = logged_in_base_client(None).await;
702 let room_id = room_id!("!r:e.uk");
703 let user_id = client.session_meta().unwrap().user_id.to_owned();
704
705 let mut room = http::response::Room::new();
708 set_room_knocked(&mut room, &user_id);
709
710 let response = response_with_room(room_id, room);
711 client
712 .process_sliding_sync(
713 &response,
714 &RequestedRequiredStates::default(),
715 &client.state_store_lock().lock().await,
716 )
717 .await
718 .expect("Failed to process sync");
719
720 let client_room = client.get_room(room_id).expect("No room found");
722 assert_eq!(client_room.state(), RoomState::Knocked);
723 }
724
725 #[async_test]
726 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
727 {
728 let client = logged_in_base_client(None).await;
730 let room_id = room_id!("!r:e.uk");
731 let user_id = user_id!("@w:e.uk");
732
733 let mut room = http::response::Room::new();
735 set_room_knocked(&mut room, user_id);
736
737 let response = response_with_room(room_id, room);
738 client
739 .process_sliding_sync(
740 &response,
741 &RequestedRequiredStates::default(),
742 &client.state_store_lock().lock().await,
743 )
744 .await
745 .expect("Failed to process sync");
746
747 let client_room = client.get_room(room_id).expect("No room found");
750 assert_eq!(client_room.state(), RoomState::Invited);
751 }
752
753 #[async_test]
754 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
755 {
756 let client = logged_in_base_client(None).await;
758 let room_id = room_id!("!r:e.uk");
759 let user_id = client.session_meta().unwrap().user_id.to_owned();
760
761 let mut room = http::response::Room::new();
763 let event = Raw::new(&json!({
764 "type": "m.room.member",
765 "sender": user_id,
766 "content": {
767 "is_direct": true,
768 "membership": "join",
769 },
770 "state_key": user_id,
771 }))
772 .expect("Failed to make raw event")
773 .cast_unchecked();
774 room.invite_state = Some(vec![event]);
775
776 let response = response_with_room(room_id, room);
777 client
778 .process_sliding_sync(
779 &response,
780 &RequestedRequiredStates::default(),
781 &client.state_store_lock().lock().await,
782 )
783 .await
784 .expect("Failed to process sync");
785
786 let client_room = client.get_room(room_id).expect("No room found");
788 assert_eq!(client_room.state(), RoomState::Invited);
789 }
790
791 #[async_test]
792 async fn test_left_a_room_from_required_state_event() {
793 let client = logged_in_base_client(None).await;
795 let room_id = room_id!("!r:e.uk");
796 let user_id = user_id!("@u:e.uk");
797
798 let mut room = http::response::Room::new();
800 set_room_joined(&mut room, user_id);
801 let response = response_with_room(room_id, room);
802 client
803 .process_sliding_sync(
804 &response,
805 &RequestedRequiredStates::default(),
806 &client.state_store_lock().lock().await,
807 )
808 .await
809 .expect("Failed to process sync");
810 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
811
812 let mut room = http::response::Room::new();
814 set_room_left(&mut room, user_id);
815 let response = response_with_room(room_id, room);
816 let sync_resp = client
817 .process_sliding_sync(
818 &response,
819 &RequestedRequiredStates::default(),
820 &client.state_store_lock().lock().await,
821 )
822 .await
823 .expect("Failed to process sync");
824
825 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
827
828 assert!(!sync_resp.rooms.joined.contains_key(room_id));
830 assert!(sync_resp.rooms.left.contains_key(room_id));
831 assert!(!sync_resp.rooms.invited.contains_key(room_id));
832 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
833 }
834
835 #[async_test]
836 async fn test_kick_or_ban_updates_room_to_left() {
837 for membership in [MembershipState::Leave, MembershipState::Ban] {
838 let room_id = room_id!("!r:e.uk");
839 let user_a_id = user_id!("@a:e.uk");
840 let user_b_id = user_id!("@b:e.uk");
841 let client = logged_in_base_client(Some(user_a_id)).await;
842
843 let mut room = http::response::Room::new();
845 set_room_joined(&mut room, user_a_id);
846 let response = response_with_room(room_id, room);
847 client
848 .process_sliding_sync(
849 &response,
850 &RequestedRequiredStates::default(),
851 &client.state_store_lock().lock().await,
852 )
853 .await
854 .expect("Failed to process sync");
855 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
856
857 let mut room = http::response::Room::new();
859 room.required_state.push(make_state_event(
860 user_b_id,
861 user_a_id.as_str(),
862 RoomMemberEventContent::new(membership.clone()),
863 None,
864 ));
865 let response = response_with_room(room_id, room);
866 let sync_resp = client
867 .process_sliding_sync(
868 &response,
869 &RequestedRequiredStates::default(),
870 &client.state_store_lock().lock().await,
871 )
872 .await
873 .expect("Failed to process sync");
874
875 match membership {
876 MembershipState::Leave => {
877 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
879 }
880 MembershipState::Ban => {
881 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
883 }
884 _ => panic!("Unexpected membership state found: {membership}"),
885 }
886
887 assert!(!sync_resp.rooms.joined.contains_key(room_id));
889 assert!(sync_resp.rooms.left.contains_key(room_id));
890 assert!(!sync_resp.rooms.invited.contains_key(room_id));
891 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
892 }
893 }
894
895 #[async_test]
896 async fn test_left_a_room_from_timeline_state_event() {
897 let client = logged_in_base_client(None).await;
899 let room_id = room_id!("!r:e.uk");
900 let user_id = user_id!("@u:e.uk");
901
902 let mut room = http::response::Room::new();
904 set_room_joined(&mut room, user_id);
905 let response = response_with_room(room_id, room);
906 client
907 .process_sliding_sync(
908 &response,
909 &RequestedRequiredStates::default(),
910 &client.state_store_lock().lock().await,
911 )
912 .await
913 .expect("Failed to process sync");
914 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
915
916 let mut room = http::response::Room::new();
918 set_room_left_as_timeline_event(&mut room, user_id);
919 let response = response_with_room(room_id, room);
920 client
921 .process_sliding_sync(
922 &response,
923 &RequestedRequiredStates::default(),
924 &client.state_store_lock().lock().await,
925 )
926 .await
927 .expect("Failed to process sync");
928
929 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
931 }
932
933 #[async_test]
934 async fn test_can_be_reinvited_to_a_left_room() {
935 let client = logged_in_base_client(None).await;
939 let room_id = room_id!("!r:e.uk");
940 let user_id = user_id!("@u:e.uk");
941
942 let mut room = http::response::Room::new();
944 set_room_joined(&mut room, user_id);
945 let response = response_with_room(room_id, room);
946 client
947 .process_sliding_sync(
948 &response,
949 &RequestedRequiredStates::default(),
950 &client.state_store_lock().lock().await,
951 )
952 .await
953 .expect("Failed to process sync");
954 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
956
957 let mut room = http::response::Room::new();
959 set_room_left(&mut room, user_id);
960 let response = response_with_room(room_id, room);
961 client
962 .process_sliding_sync(
963 &response,
964 &RequestedRequiredStates::default(),
965 &client.state_store_lock().lock().await,
966 )
967 .await
968 .expect("Failed to process sync");
969 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
971
972 let mut room = http::response::Room::new();
974 set_room_invited(&mut room, user_id, user_id);
975 let response = response_with_room(room_id, room);
976 client
977 .process_sliding_sync(
978 &response,
979 &RequestedRequiredStates::default(),
980 &client.state_store_lock().lock().await,
981 )
982 .await
983 .expect("Failed to process sync");
984
985 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
987 }
988
989 #[async_test]
990 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
991 let room_id = room_id!("!r:e.uk");
992 let user_a_id = user_id!("@a:e.uk");
993 let user_b_id = user_id!("@b:e.uk");
994
995 let client = logged_in_base_client(None).await;
997 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
998
999 assert!(
1001 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1002 );
1003 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1004
1005 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1007
1008 assert!(
1012 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1013 );
1014 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1015 }
1016
1017 #[async_test]
1018 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
1019 {
1020 let room_id = room_id!("!r:e.uk");
1021 let user_a_id = user_id!("@a:e.uk");
1022 let user_b_id = user_id!("@b:e.uk");
1023
1024 let client = logged_in_base_client(None).await;
1026 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1027
1028 assert!(
1030 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1031 );
1032 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1033
1034 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1036
1037 assert!(
1041 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1042 );
1043 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1044 }
1045
1046 #[async_test]
1047 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1048 let room_id = room_id!("!r:bar.org");
1049 let user_a_id = user_id!("@a:bar.org");
1050 let user_b_id = user_id!("@b:bar.org");
1051
1052 let client = logged_in_base_client(None).await;
1054 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1055
1056 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1058
1059 assert!(
1061 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1062 );
1063 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1064
1065 let room = client.get_room(room_id).unwrap();
1066
1067 assert_eq!(room.active_members_count(), 2);
1068 assert_eq!(room.joined_members_count(), 2);
1069 assert_eq!(room.invited_members_count(), 0);
1070 }
1071
1072 #[async_test]
1073 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1074 let room_id = room_id!("!r:bar.org");
1075 let user_a_id = user_id!("@a:bar.org");
1076 let user_b_id = user_id!("@b:bar.org");
1077
1078 let client = logged_in_base_client(None).await;
1080 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1081
1082 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1084
1085 assert!(
1087 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1088 );
1089 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1090
1091 let room = client.get_room(room_id).unwrap();
1092
1093 assert_eq!(room.active_members_count(), 2);
1094 assert_eq!(room.joined_members_count(), 1);
1095 assert_eq!(room.invited_members_count(), 1);
1096 }
1097
1098 #[async_test]
1099 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1100 let client = logged_in_base_client(None).await;
1102 let room_id = room_id!("!r:e.uk");
1103
1104 let room = {
1106 let mut room = http::response::Room::new();
1107 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1108
1109 room
1110 };
1111 let response = response_with_room(room_id, room);
1112 client
1113 .process_sliding_sync(
1114 &response,
1115 &RequestedRequiredStates::default(),
1116 &client.state_store_lock().lock().await,
1117 )
1118 .await
1119 .expect("Failed to process sync");
1120
1121 let client_room = client.get_room(room_id).expect("No room found");
1123 assert_eq!(
1124 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1125 "med1"
1126 );
1127 }
1128
1129 #[async_test]
1130 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1131 let client = logged_in_base_client(None).await;
1133 let room_id = room_id!("!r:e.uk");
1134
1135 let room = {
1139 let mut room = http::response::Room::new();
1140 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1141
1142 room
1143 };
1144 let response = response_with_room(room_id, room);
1145 client
1146 .process_sliding_sync(
1147 &response,
1148 &RequestedRequiredStates::default(),
1149 &client.state_store_lock().lock().await,
1150 )
1151 .await
1152 .expect("Failed to process sync");
1153
1154 let client_room = client.get_room(room_id).expect("No room found");
1156 assert_eq!(
1157 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1158 "med1"
1159 );
1160
1161 let room = http::response::Room::new();
1165 let response = response_with_room(room_id, room);
1166 client
1167 .process_sliding_sync(
1168 &response,
1169 &RequestedRequiredStates::default(),
1170 &client.state_store_lock().lock().await,
1171 )
1172 .await
1173 .expect("Failed to process sync");
1174
1175 let client_room = client.get_room(room_id).expect("No room found");
1177 assert_eq!(
1178 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1179 "med1"
1180 );
1181
1182 let room = {
1186 let mut room = http::response::Room::new();
1187 room.avatar = JsOption::Null;
1188
1189 room
1190 };
1191 let response = response_with_room(room_id, room);
1192 client
1193 .process_sliding_sync(
1194 &response,
1195 &RequestedRequiredStates::default(),
1196 &client.state_store_lock().lock().await,
1197 )
1198 .await
1199 .expect("Failed to process sync");
1200
1201 let client_room = client.get_room(room_id).expect("No room found");
1203 assert!(client_room.avatar_url().is_none());
1204 }
1205
1206 #[async_test]
1207 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1208 let client = logged_in_base_client(None).await;
1210 let room_id = room_id!("!r:e.uk");
1211 let user_id = user_id!("@u:e.uk");
1212
1213 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1215 let response = response_with_room(room_id, room);
1216 client
1217 .process_sliding_sync(
1218 &response,
1219 &RequestedRequiredStates::default(),
1220 &client.state_store_lock().lock().await,
1221 )
1222 .await
1223 .expect("Failed to process sync");
1224
1225 let client_room = client.get_room(room_id).expect("No room found");
1227 assert_eq!(
1228 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1229 "med1"
1230 );
1231 }
1232
1233 #[async_test]
1234 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1235 let client = logged_in_base_client(None).await;
1237 let room_id = room_id!("!r:e.uk");
1238 let user_id = user_id!("@u:e.uk");
1239
1240 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1241
1242 let mut room = http::response::Room::new();
1244 set_room_invited(&mut room, user_id, user_id);
1245 let response = response_with_room(room_id, room);
1246 let sync_resp = client
1247 .process_sliding_sync(
1248 &response,
1249 &RequestedRequiredStates::default(),
1250 &client.state_store_lock().lock().await,
1251 )
1252 .await
1253 .expect("Failed to process sync");
1254
1255 let client_room = client.get_room(room_id).expect("No room found");
1257 assert_eq!(client_room.room_id(), room_id);
1258 assert_eq!(client_room.state(), RoomState::Invited);
1259
1260 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1262 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1263
1264 assert_matches!(
1265 room_info_notable_update.recv().await,
1266 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1267 assert_eq!(received_room_id, room_id);
1268 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1270 }
1271 );
1272 assert_matches!(
1273 room_info_notable_update.recv().await,
1274 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1275 assert_eq!(received_room_id, room_id);
1276 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1278 }
1279 );
1280 assert!(room_info_notable_update.is_empty());
1281 }
1282
1283 #[async_test]
1284 async fn test_knock_room_is_added_to_client_and_knock_list() {
1285 let client = logged_in_base_client(None).await;
1287 let room_id = room_id!("!r:e.uk");
1288 let user_id = user_id!("@u:e.uk");
1289
1290 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1291
1292 let mut room = http::response::Room::new();
1294 set_room_knocked(&mut room, user_id);
1295 let response = response_with_room(room_id, room);
1296 let sync_resp = client
1297 .process_sliding_sync(
1298 &response,
1299 &RequestedRequiredStates::default(),
1300 &client.state_store_lock().lock().await,
1301 )
1302 .await
1303 .expect("Failed to process sync");
1304
1305 let client_room = client.get_room(room_id).expect("No room found");
1307 assert_eq!(client_room.room_id(), room_id);
1308 assert_eq!(client_room.state(), RoomState::Knocked);
1309
1310 assert!(!sync_resp.rooms.knocked[room_id].knock_state.is_empty());
1312 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1313
1314 assert_matches!(
1315 room_info_notable_update.recv().await,
1316 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1317 assert_eq!(received_room_id, room_id);
1318 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1320 }
1321 );
1322 assert_matches!(
1323 room_info_notable_update.recv().await,
1324 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1325 assert_eq!(received_room_id, room_id);
1326 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1328 }
1329 );
1330 assert!(room_info_notable_update.is_empty());
1331 }
1332
1333 #[async_test]
1334 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1335 let client = logged_in_base_client(None).await;
1337 let room_id = room_id!("!r:e.uk");
1338 let user_id = user_id!("@u:e.uk");
1339
1340 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1342 set_room_invited(&mut room, user_id, user_id);
1343 let response = response_with_room(room_id, room);
1344 client
1345 .process_sliding_sync(
1346 &response,
1347 &RequestedRequiredStates::default(),
1348 &client.state_store_lock().lock().await,
1349 )
1350 .await
1351 .expect("Failed to process sync");
1352
1353 let client_room = client.get_room(room_id).expect("No room found");
1355 assert_eq!(
1356 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1357 "med1"
1358 );
1359 }
1360
1361 #[async_test]
1362 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1363 {
1364 let client = logged_in_base_client(None).await;
1366 let room_id = room_id!("!r:e.uk");
1367 let user_id = user_id!("@u:e.uk");
1368 let room_alias_id = room_alias_id!("#myroom:e.uk");
1369
1370 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1372 set_room_invited(&mut room, user_id, user_id);
1373 let response = response_with_room(room_id, room);
1374 client
1375 .process_sliding_sync(
1376 &response,
1377 &RequestedRequiredStates::default(),
1378 &client.state_store_lock().lock().await,
1379 )
1380 .await
1381 .expect("Failed to process sync");
1382
1383 let client_room = client.get_room(room_id).expect("No room found");
1385 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1386 }
1387
1388 #[async_test]
1389 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1390 let client = logged_in_base_client(None).await;
1392 let room_id = room_id!("!r:e.uk");
1393 let user_id = user_id!("@u:e.uk");
1394 let room_alias_id = room_alias_id!("#myroom:e.uk");
1395
1396 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1399 room.name = Some("This came from the server".to_owned());
1400 let response = response_with_room(room_id, room);
1401 client
1402 .process_sliding_sync(
1403 &response,
1404 &RequestedRequiredStates::default(),
1405 &client.state_store_lock().lock().await,
1406 )
1407 .await
1408 .expect("Failed to process sync");
1409
1410 let client_room = client.get_room(room_id).expect("No room found");
1412 assert_eq!(
1413 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1414 "myroom"
1415 );
1416 assert!(client_room.name().is_none());
1417 }
1418
1419 #[async_test]
1420 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1421 let client = logged_in_base_client(None).await;
1422 let user_id = user_id!("@u:e.uk");
1423 let room_id = room_id!("!r:e.uk");
1424
1425 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1426
1427 let room = room_with_name("Hello World", user_id);
1428 let response = response_with_room(room_id, room);
1429 client
1430 .process_sliding_sync(
1431 &response,
1432 &RequestedRequiredStates::default(),
1433 &client.state_store_lock().lock().await,
1434 )
1435 .await
1436 .expect("Failed to process sync");
1437
1438 let room = client.get_room(room_id).expect("No room found");
1439 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1440
1441 assert_matches!(
1442 room_info_notable_update.recv().await,
1443 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1444 assert_eq!(received_room_id, room_id);
1445 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1446 }
1447 );
1448 assert_matches!(
1449 room_info_notable_update.recv().await,
1450 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1451 assert_eq!(received_room_id, room_id);
1452 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1454 }
1455 );
1456 assert!(room_info_notable_update.is_empty());
1457 }
1458
1459 #[async_test]
1460 async fn test_display_name_is_persisted_from_sliding_sync() {
1461 let user_id = user_id!("@u:e.uk");
1462 let room_id = room_id!("!r:e.uk");
1463 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1464 let state_store;
1465
1466 {
1467 let client = {
1468 let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1469 state_store = store.state_store.clone();
1470
1471 let client =
1472 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1473 client
1474 .activate(
1475 session_meta.clone(),
1476 RoomLoadSettings::default(),
1477 #[cfg(feature = "e2e-encryption")]
1478 None,
1479 )
1480 .await
1481 .expect("`activate` failed!");
1482
1483 client
1484 };
1485
1486 let room = room_with_name("Hello World", user_id);
1489 let response = response_with_room(room_id, room);
1490 client
1491 .process_sliding_sync(
1492 &response,
1493 &RequestedRequiredStates::default(),
1494 &client.state_store_lock().lock().await,
1495 )
1496 .await
1497 .expect("Failed to process sync");
1498
1499 let room = client.get_room(room_id).expect("No room found");
1500 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1501 }
1502
1503 {
1504 let client = {
1505 let mut store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1506 store.state_store = state_store;
1507 let client =
1508 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1509 client
1510 .activate(
1511 session_meta,
1512 RoomLoadSettings::default(),
1513 #[cfg(feature = "e2e-encryption")]
1514 None,
1515 )
1516 .await
1517 .expect("`activate` failed!");
1518
1519 client
1520 };
1521
1522 let room = client.get_room(room_id).expect("No room found");
1523 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1524 }
1525 }
1526
1527 #[async_test]
1528 async fn test_compute_heroes_from_sliding_sync() {
1529 let client = logged_in_base_client(None).await;
1531 let room_id = room_id!("!r:e.uk");
1532 let gordon = owned_user_id!("@gordon:e.uk");
1533 let alice = owned_user_id!("@alice:e.uk");
1534
1535 let mut room = http::response::Room::new();
1538 room.heroes = Some(vec![
1539 assign!(http::response::Hero::new(gordon), {
1540 name: Some("Gordon".to_owned()),
1541 }),
1542 assign!(http::response::Hero::new(alice), {
1543 name: Some("Alice".to_owned()),
1544 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1545 }),
1546 ]);
1547 let response = response_with_room(room_id, room);
1548 let _sync_resp = client
1549 .process_sliding_sync(
1550 &response,
1551 &RequestedRequiredStates::default(),
1552 &client.state_store_lock().lock().await,
1553 )
1554 .await
1555 .expect("Failed to process sync");
1556
1557 let client_room = client.get_room(room_id).expect("No room found");
1559 assert_eq!(client_room.room_id(), room_id);
1560 assert_eq!(client_room.state(), RoomState::Joined);
1561
1562 assert_eq!(
1564 client_room.clone_info().summary.heroes(),
1565 &[
1566 RoomHero {
1567 user_id: owned_user_id!("@gordon:e.uk"),
1568 display_name: Some("Gordon".to_owned()),
1569 avatar_url: None
1570 },
1571 RoomHero {
1572 user_id: owned_user_id!("@alice:e.uk"),
1573 display_name: Some("Alice".to_owned()),
1574 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1575 },
1576 ]
1577 );
1578 }
1579
1580 #[async_test]
1581 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1582 let client = logged_in_base_client(None).await;
1584 let room_id = room_id!("!r:e.uk");
1585
1586 let room = assign!(http::response::Room::new(), {
1588 bump_stamp: Some(42u32.into()),
1589 });
1590 let response = response_with_room(room_id, room);
1591 client
1592 .process_sliding_sync(
1593 &response,
1594 &RequestedRequiredStates::default(),
1595 &client.state_store_lock().lock().await,
1596 )
1597 .await
1598 .expect("Failed to process sync");
1599
1600 let client_room = client.get_room(room_id).expect("No room found");
1602 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1603 }
1604
1605 #[async_test]
1606 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1607 let client = logged_in_base_client(None).await;
1609 let room_id = room_id!("!r:e.uk");
1610
1611 {
1612 let room = assign!(http::response::Room::new(), {
1614 bump_stamp: Some(42u32.into()),
1615 });
1616 let response = response_with_room(room_id, room);
1617 client
1618 .process_sliding_sync(
1619 &response,
1620 &RequestedRequiredStates::default(),
1621 &client.state_store_lock().lock().await,
1622 )
1623 .await
1624 .expect("Failed to process sync");
1625
1626 let client_room = client.get_room(room_id).expect("No room found");
1628 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1629 }
1630
1631 {
1632 let room = assign!(http::response::Room::new(), {
1634 bump_stamp: None,
1635 });
1636 let response = response_with_room(room_id, room);
1637 client
1638 .process_sliding_sync(
1639 &response,
1640 &RequestedRequiredStates::default(),
1641 &client.state_store_lock().lock().await,
1642 )
1643 .await
1644 .expect("Failed to process sync");
1645
1646 let client_room = client.get_room(room_id).expect("No room found");
1648 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1649 }
1650
1651 {
1652 let room = assign!(http::response::Room::new(), {
1655 bump_stamp: Some(153u32.into()),
1656 });
1657 let response = response_with_room(room_id, room);
1658 client
1659 .process_sliding_sync(
1660 &response,
1661 &RequestedRequiredStates::default(),
1662 &client.state_store_lock().lock().await,
1663 )
1664 .await
1665 .expect("Failed to process sync");
1666
1667 let client_room = client.get_room(room_id).expect("No room found");
1669 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1670 }
1671 }
1672
1673 #[async_test]
1674 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1675 let client = logged_in_base_client(None).await;
1677 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1678 let room_id = room_id!("!r:e.uk");
1679
1680 let room = assign!(http::response::Room::new(), {
1682 bump_stamp: Some(42u32.into()),
1683 });
1684 let response = response_with_room(room_id, room);
1685 client
1686 .process_sliding_sync(
1687 &response,
1688 &RequestedRequiredStates::default(),
1689 &client.state_store_lock().lock().await,
1690 )
1691 .await
1692 .expect("Failed to process sync");
1693
1694 assert_matches!(
1697 room_info_notable_update_stream.recv().await,
1698 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1699 assert_eq!(received_room_id, room_id);
1700 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1701 }
1702 );
1703 assert_matches!(
1704 room_info_notable_update_stream.recv().await,
1705 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1706 assert_eq!(received_room_id, room_id);
1707 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1708 }
1709 );
1710 assert!(room_info_notable_update_stream.is_empty());
1711
1712 let room = assign!(http::response::Room::new(), {
1714 bump_stamp: Some(43u32.into()),
1715 });
1716 let response = response_with_room(room_id, room);
1717 client
1718 .process_sliding_sync(
1719 &response,
1720 &RequestedRequiredStates::default(),
1721 &client.state_store_lock().lock().await,
1722 )
1723 .await
1724 .expect("Failed to process sync");
1725
1726 assert_matches!(
1728 room_info_notable_update_stream.recv().await,
1729 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1730 assert_eq!(received_room_id, room_id);
1731 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1732 }
1733 );
1734 assert!(room_info_notable_update_stream.is_empty());
1735 }
1736
1737 #[async_test]
1738 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1739 let client = logged_in_base_client(None).await;
1741 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1742
1743 let room_id = room_id!("!r:e.uk");
1745 let room = http::response::Room::new();
1746 let response = response_with_room(room_id, room);
1747 client
1748 .process_sliding_sync(
1749 &response,
1750 &RequestedRequiredStates::default(),
1751 &client.state_store_lock().lock().await,
1752 )
1753 .await
1754 .expect("Failed to process sync");
1755
1756 assert_matches!(
1758 room_info_notable_update_stream.recv().await,
1759 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1760 assert_eq!(received_room_id, room_id);
1761 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1762 }
1763 );
1764 assert_matches!(
1765 room_info_notable_update_stream.recv().await,
1766 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1767 assert_eq!(received_room_id, room_id);
1768 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1769 }
1770 );
1771
1772 let room_id = room_id!("!r:e.uk");
1774 let events = vec![
1775 Raw::from_json_string(
1776 json!({
1777 "type": "m.room.member",
1778 "event_id": "$3",
1779 "content": { "membership": "join" },
1780 "sender": "@u:h.uk",
1781 "origin_server_ts": 12344445,
1782 "state_key": "@u:e.uk",
1783 })
1784 .to_string(),
1785 )
1786 .unwrap(),
1787 ];
1788 let room = assign!(http::response::Room::new(), {
1789 required_state: events,
1790 });
1791 let response = response_with_room(room_id, room);
1792 client
1793 .process_sliding_sync(
1794 &response,
1795 &RequestedRequiredStates::default(),
1796 &client.state_store_lock().lock().await,
1797 )
1798 .await
1799 .expect("Failed to process sync");
1800
1801 assert_matches!(
1803 room_info_notable_update_stream.recv().await,
1804 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1805 assert_eq!(received_room_id, room_id);
1806 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1807 }
1808 );
1809 assert!(room_info_notable_update_stream.is_empty());
1810
1811 let events = vec![
1812 Raw::from_json_string(
1813 json!({
1814 "type": "m.room.member",
1815 "event_id": "$3",
1816 "content": { "membership": "leave" },
1817 "sender": "@u:h.uk",
1818 "origin_server_ts": 12344445,
1819 "state_key": "@u:e.uk",
1820 })
1821 .to_string(),
1822 )
1823 .unwrap(),
1824 ];
1825 let room = assign!(http::response::Room::new(), {
1826 required_state: events,
1827 });
1828 let response = response_with_room(room_id, room);
1829 client
1830 .process_sliding_sync(
1831 &response,
1832 &RequestedRequiredStates::default(),
1833 &client.state_store_lock().lock().await,
1834 )
1835 .await
1836 .expect("Failed to process sync");
1837
1838 assert_matches!(
1840 room_info_notable_update_stream.recv().await,
1841 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1842 assert_eq!(received_room_id, room_id);
1843 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1844 }
1845 );
1846 assert!(room_info_notable_update_stream.is_empty());
1847 }
1848
1849 #[async_test]
1850 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1851 let client = logged_in_base_client(None).await;
1853 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1854
1855 let room_id = room_id!("!r:e.uk");
1857 let room = http::response::Room::new();
1858 let response = response_with_room(room_id, room);
1859 client
1860 .process_sliding_sync(
1861 &response,
1862 &RequestedRequiredStates::default(),
1863 &client.state_store_lock().lock().await,
1864 )
1865 .await
1866 .expect("Failed to process sync");
1867
1868 assert_matches!(
1870 room_info_notable_update_stream.recv().await,
1871 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1872 assert_eq!(received_room_id, room_id);
1873 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1874 }
1875 );
1876 assert_matches!(
1877 room_info_notable_update_stream.recv().await,
1878 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1879 assert_eq!(received_room_id, room_id);
1880 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
1881 }
1882 );
1883 assert!(room_info_notable_update_stream.is_empty());
1884
1885 let room_id = room_id!("!r:e.uk");
1888 let room_account_data_events = vec![
1889 Raw::from_json_string(
1890 json!({
1891 "type": "m.marked_unread",
1892 "event_id": "$1",
1893 "content": { "unread": true },
1894 "sender": client.session_meta().unwrap().user_id,
1895 "origin_server_ts": 12344445,
1896 })
1897 .to_string(),
1898 )
1899 .unwrap(),
1900 ];
1901 let mut response = response_with_room(room_id, http::response::Room::new());
1902 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1903
1904 client
1905 .process_sliding_sync(
1906 &response,
1907 &RequestedRequiredStates::default(),
1908 &client.state_store_lock().lock().await,
1909 )
1910 .await
1911 .expect("Failed to process sync");
1912
1913 assert_matches!(
1915 room_info_notable_update_stream.recv().await,
1916 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1917 assert_eq!(received_room_id, room_id);
1918 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
1919 }
1920 );
1921
1922 client
1924 .process_sliding_sync(
1925 &response,
1926 &RequestedRequiredStates::default(),
1927 &client.state_store_lock().lock().await,
1928 )
1929 .await
1930 .expect("Failed to process sync");
1931
1932 assert_matches!(
1933 room_info_notable_update_stream.recv().await,
1934 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1935 assert_eq!(received_room_id, room_id);
1936 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1937 }
1938 );
1939 assert!(room_info_notable_update_stream.is_empty());
1940
1941 let room_account_data_events = vec![
1943 Raw::from_json_string(
1944 json!({
1945 "type": "m.marked_unread",
1946 "event_id": "$1",
1947 "content": { "unread": false },
1948 "sender": client.session_meta().unwrap().user_id,
1949 "origin_server_ts": 12344445,
1950 })
1951 .to_string(),
1952 )
1953 .unwrap(),
1954 ];
1955 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1956 client
1957 .process_sliding_sync(
1958 &response,
1959 &RequestedRequiredStates::default(),
1960 &client.state_store_lock().lock().await,
1961 )
1962 .await
1963 .expect("Failed to process sync");
1964
1965 assert_matches!(
1966 room_info_notable_update_stream.recv().await,
1967 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1968 assert_eq!(received_room_id, room_id);
1969 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
1970 }
1971 );
1972 assert!(room_info_notable_update_stream.is_empty());
1973 }
1974
1975 #[async_test]
1976 async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
1977 let client = logged_in_base_client(None).await;
1979 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1980
1981 let room_id = room_id!("!r:e.uk");
1983 let room = http::response::Room::new();
1984 let response = response_with_room(room_id, room);
1985 client
1986 .process_sliding_sync(
1987 &response,
1988 &RequestedRequiredStates::default(),
1989 &client.state_store_lock().lock().await,
1990 )
1991 .await
1992 .expect("Failed to process sync");
1993
1994 assert_matches!(
1996 room_info_notable_update_stream.recv().await,
1997 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1998 assert_eq!(received_room_id, room_id);
1999 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2000 }
2001 );
2002 assert_matches!(
2003 room_info_notable_update_stream.recv().await,
2004 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2005 assert_eq!(received_room_id, room_id);
2006 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2007 }
2008 );
2009 assert!(room_info_notable_update_stream.is_empty());
2010
2011 let room_account_data_events = vec![
2014 Raw::from_json_string(
2015 json!({
2016 "type": "m.fully_read",
2017 "content": { "event_id": "$first" },
2018 })
2019 .to_string(),
2020 )
2021 .unwrap(),
2022 ];
2023 let mut response = response_with_room(room_id, http::response::Room::new());
2024 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2025
2026 client
2027 .process_sliding_sync(
2028 &response,
2029 &RequestedRequiredStates::default(),
2030 &client.state_store_lock().lock().await,
2031 )
2032 .await
2033 .expect("Failed to process sync");
2034
2035 assert_matches!(
2037 room_info_notable_update_stream.recv().await,
2038 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2039 assert_eq!(received_room_id, room_id);
2040 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2041 }
2042 );
2043
2044 let room = client.get_room(room_id).expect("room should exist");
2045 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2046
2047 client
2049 .process_sliding_sync(
2050 &response,
2051 &RequestedRequiredStates::default(),
2052 &client.state_store_lock().lock().await,
2053 )
2054 .await
2055 .expect("Failed to process sync");
2056
2057 assert_matches!(
2058 room_info_notable_update_stream.recv().await,
2059 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2060 assert_eq!(received_room_id, room_id);
2061 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2062 }
2063 );
2064 assert!(room_info_notable_update_stream.is_empty());
2065
2066 let room_account_data_events = vec![
2068 Raw::from_json_string(
2069 json!({
2070 "type": "m.fully_read",
2071 "content": { "event_id": "$second" },
2072 })
2073 .to_string(),
2074 )
2075 .unwrap(),
2076 ];
2077 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2078 client
2079 .process_sliding_sync(
2080 &response,
2081 &RequestedRequiredStates::default(),
2082 &client.state_store_lock().lock().await,
2083 )
2084 .await
2085 .expect("Failed to process sync");
2086
2087 assert_matches!(
2088 room_info_notable_update_stream.recv().await,
2089 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2090 assert_eq!(received_room_id, room_id);
2091 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2092 }
2093 );
2094 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2095 assert!(room_info_notable_update_stream.is_empty());
2096 }
2097
2098 #[async_test]
2099 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2100 let client = logged_in_base_client(None).await;
2102 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2103
2104 let room_id = room_id!("!r:e.uk");
2106 let room = http::response::Room::new();
2107 let response = response_with_room(room_id, room);
2108 client
2109 .process_sliding_sync(
2110 &response,
2111 &RequestedRequiredStates::default(),
2112 &client.state_store_lock().lock().await,
2113 )
2114 .await
2115 .expect("Failed to process sync");
2116
2117 assert_matches!(
2119 room_info_notable_update_stream.recv().await,
2120 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2121 assert_eq!(received_room_id, room_id);
2122 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2123 }
2124 );
2125 assert_matches!(
2126 room_info_notable_update_stream.recv().await,
2127 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2128 assert_eq!(received_room_id, room_id);
2129 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2130 }
2131 );
2132 assert!(room_info_notable_update_stream.is_empty());
2133
2134 let room_id = room_id!("!r:e.uk");
2137 let unstable_room_account_data_events = vec![
2138 Raw::from_json_string(
2139 json!({
2140 "type": "com.famedly.marked_unread",
2141 "event_id": "$1",
2142 "content": { "unread": true },
2143 "sender": client.session_meta().unwrap().user_id,
2144 "origin_server_ts": 12344445,
2145 })
2146 .to_string(),
2147 )
2148 .unwrap(),
2149 ];
2150 let mut response = response_with_room(room_id, http::response::Room::new());
2151 response
2152 .extensions
2153 .account_data
2154 .rooms
2155 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2156
2157 client
2158 .process_sliding_sync(
2159 &response,
2160 &RequestedRequiredStates::default(),
2161 &client.state_store_lock().lock().await,
2162 )
2163 .await
2164 .expect("Failed to process sync");
2165
2166 assert_matches!(
2168 room_info_notable_update_stream.recv().await,
2169 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2170 assert_eq!(received_room_id, room_id);
2171 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2172 }
2173 );
2174 assert!(room_info_notable_update_stream.is_empty());
2175
2176 let stable_room_account_data_events = vec![
2178 Raw::from_json_string(
2179 json!({
2180 "type": "m.marked_unread",
2181 "event_id": "$1",
2182 "content": { "unread": false },
2183 "sender": client.session_meta().unwrap().user_id,
2184 "origin_server_ts": 12344445,
2185 })
2186 .to_string(),
2187 )
2188 .unwrap(),
2189 ];
2190 response
2191 .extensions
2192 .account_data
2193 .rooms
2194 .insert(room_id.to_owned(), stable_room_account_data_events);
2195 client
2196 .process_sliding_sync(
2197 &response,
2198 &RequestedRequiredStates::default(),
2199 &client.state_store_lock().lock().await,
2200 )
2201 .await
2202 .expect("Failed to process sync");
2203
2204 assert_matches!(
2206 room_info_notable_update_stream.recv().await,
2207 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2208 assert_eq!(received_room_id, room_id);
2209 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2210 }
2211 );
2212 assert!(room_info_notable_update_stream.is_empty());
2213
2214 response
2217 .extensions
2218 .account_data
2219 .rooms
2220 .insert(room_id.to_owned(), unstable_room_account_data_events);
2221 client
2222 .process_sliding_sync(
2223 &response,
2224 &RequestedRequiredStates::default(),
2225 &client.state_store_lock().lock().await,
2226 )
2227 .await
2228 .expect("Failed to process sync");
2229
2230 assert_matches!(
2232 room_info_notable_update_stream.recv().await,
2233 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2234 assert_eq!(received_room_id, room_id);
2235 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2236 }
2237 );
2238 assert!(room_info_notable_update_stream.is_empty());
2239
2240 let stable_room_account_data_events = vec![
2243 Raw::from_json_string(
2244 json!({
2245 "type": "m.marked_unread",
2246 "event_id": "$3",
2247 "content": { "unread": true },
2248 "sender": client.session_meta().unwrap().user_id,
2249 "origin_server_ts": 12344445,
2250 })
2251 .to_string(),
2252 )
2253 .unwrap(),
2254 ];
2255 response
2256 .extensions
2257 .account_data
2258 .rooms
2259 .insert(room_id.to_owned(), stable_room_account_data_events);
2260 client
2261 .process_sliding_sync(
2262 &response,
2263 &RequestedRequiredStates::default(),
2264 &client.state_store_lock().lock().await,
2265 )
2266 .await
2267 .expect("Failed to process sync");
2268
2269 assert_matches!(
2271 room_info_notable_update_stream.recv().await,
2272 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2273 assert_eq!(received_room_id, room_id);
2274 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2275 }
2276 );
2277 assert!(room_info_notable_update_stream.is_empty());
2278 }
2279
2280 #[async_test]
2281 async fn test_pinned_events_are_updated_on_sync() {
2282 let user_a_id = user_id!("@a:e.uk");
2283 let client = logged_in_base_client(Some(user_a_id)).await;
2284 let room_id = room_id!("!r:e.uk");
2285 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2286
2287 let mut room_response = http::response::Room::new();
2289 set_room_joined(&mut room_response, user_a_id);
2290 let response = response_with_room(room_id, room_response);
2291 client
2292 .process_sliding_sync(
2293 &response,
2294 &RequestedRequiredStates::default(),
2295 &client.state_store_lock().lock().await,
2296 )
2297 .await
2298 .expect("Failed to process sync");
2299
2300 let room = client.get_room(room_id).unwrap();
2302 let pinned_event_ids = room.pinned_event_ids();
2303 assert_matches!(pinned_event_ids, None);
2304
2305 let mut room_response = http::response::Room::new();
2307 room_response.required_state.push(make_state_event(
2308 user_a_id,
2309 "",
2310 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2311 None,
2312 ));
2313 let response = response_with_room(room_id, room_response);
2314 client
2315 .process_sliding_sync(
2316 &response,
2317 &RequestedRequiredStates::default(),
2318 &client.state_store_lock().lock().await,
2319 )
2320 .await
2321 .expect("Failed to process sync");
2322
2323 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2324 assert_eq!(pinned_event_ids.len(), 1);
2325 assert_eq!(pinned_event_ids[0], pinned_event_id);
2326
2327 let mut room_response = http::response::Room::new();
2329 room_response.required_state.push(make_state_event(
2330 user_a_id,
2331 "",
2332 RoomPinnedEventsEventContent::new(Vec::new()),
2333 None,
2334 ));
2335 let response = response_with_room(room_id, room_response);
2336 client
2337 .process_sliding_sync(
2338 &response,
2339 &RequestedRequiredStates::default(),
2340 &client.state_store_lock().lock().await,
2341 )
2342 .await
2343 .expect("Failed to process sync");
2344 let pinned_event_ids = room.pinned_event_ids().unwrap();
2345 assert!(pinned_event_ids.is_empty());
2346 }
2347
2348 #[async_test]
2349 async fn test_dms_are_processed_in_any_sync_response() {
2350 let current_user_id = user_id!("@current:e.uk");
2351 let client = logged_in_base_client(Some(current_user_id)).await;
2352 let user_a_id = user_id!("@a:e.uk");
2353 let user_b_id = user_id!("@b:e.uk");
2354 let room_id_1 = room_id!("!r:e.uk");
2355 let room_id_2 = room_id!("!s:e.uk");
2356
2357 let mut room_response = http::response::Room::new();
2358 set_room_joined(&mut room_response, user_a_id);
2359 let mut response = response_with_room(room_id_1, room_response);
2360 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2361 BTreeMap::new();
2362 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2363 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2364 response
2365 .extensions
2366 .account_data
2367 .global
2368 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2369 client
2370 .process_sliding_sync(
2371 &response,
2372 &RequestedRequiredStates::default(),
2373 &client.state_store_lock().lock().await,
2374 )
2375 .await
2376 .expect("Failed to process sync");
2377
2378 let room_1 = client.get_room(room_id_1).unwrap();
2379 assert!(room_1.is_direct().await.unwrap());
2380
2381 let mut room_response = http::response::Room::new();
2383 set_room_joined(&mut room_response, user_b_id);
2384 let response = response_with_room(room_id_2, room_response);
2385 client
2386 .process_sliding_sync(
2387 &response,
2388 &RequestedRequiredStates::default(),
2389 &client.state_store_lock().lock().await,
2390 )
2391 .await
2392 .expect("Failed to process sync");
2393
2394 let room_2 = client.get_room(room_id_2).unwrap();
2395 assert!(room_2.is_direct().await.unwrap());
2396 }
2397
2398 #[async_test]
2399 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2400 let user_id = user_id!("@raclette:patate");
2401 let client = logged_in_base_client(Some(user_id)).await;
2402 let room_id_0 = room_id!("!r0");
2403 let room_id_1 = room_id!("!r1");
2404 let room_id_2 = room_id!("!r2");
2405
2406 let requested_required_states = RequestedRequiredStates::from(&{
2423 let mut request = http::Request::new();
2424
2425 request.room_subscriptions.insert(room_id_0.to_owned(), {
2426 let mut room_subscription = http::request::RoomSubscription::default();
2427
2428 room_subscription
2429 .required_state
2430 .push((StateEventType::RoomEncryption, "".to_owned()));
2431
2432 room_subscription
2433 });
2434
2435 request
2436 });
2437
2438 let mut response = http::Response::new("0".to_owned());
2439
2440 {
2444 let not_encrypted_room = http::response::Room::new();
2445 let mut encrypted_room = http::response::Room::new();
2446 set_room_is_encrypted(&mut encrypted_room, user_id);
2447
2448 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2449 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2450 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2451 }
2452
2453 client
2454 .process_sliding_sync(
2455 &response,
2456 &requested_required_states,
2457 &client.state_store_lock().lock().await,
2458 )
2459 .await
2460 .expect("Failed to process sync");
2461
2462 assert_matches!(
2464 client.get_room(room_id_0).unwrap().encryption_state(),
2465 EncryptionState::Encrypted
2466 );
2467 assert_matches!(
2468 client.get_room(room_id_1).unwrap().encryption_state(),
2469 EncryptionState::Encrypted
2470 );
2471 assert_matches!(
2473 client.get_room(room_id_2).unwrap().encryption_state(),
2474 EncryptionState::NotEncrypted
2475 )
2476 }
2477
2478 #[async_test]
2479 async fn test_room_encryption_state_is_unknown() {
2480 let user_id = user_id!("@raclette:patate");
2481 let client = logged_in_base_client(Some(user_id)).await;
2482 let room_id_0 = room_id!("!r0");
2483 let room_id_1 = room_id!("!r1");
2484
2485 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2498
2499 let mut response = http::Response::new("0".to_owned());
2500
2501 {
2503 let not_encrypted_room = http::response::Room::new();
2504 let mut encrypted_room = http::response::Room::new();
2505 set_room_is_encrypted(&mut encrypted_room, user_id);
2506
2507 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2508 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2509 }
2510
2511 client
2512 .process_sliding_sync(
2513 &response,
2514 &requested_required_states,
2515 &client.state_store_lock().lock().await,
2516 )
2517 .await
2518 .expect("Failed to process sync");
2519
2520 assert_matches!(
2523 client.get_room(room_id_0).unwrap().encryption_state(),
2524 EncryptionState::Encrypted
2525 );
2526 assert_matches!(
2529 client.get_room(room_id_1).unwrap().encryption_state(),
2530 EncryptionState::Unknown
2531 );
2532 }
2533
2534 async fn membership(
2535 client: &BaseClient,
2536 room_id: &RoomId,
2537 user_id: &UserId,
2538 ) -> MembershipState {
2539 let room = client.get_room(room_id).expect("Room not found!");
2540 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2541 member.membership().clone()
2542 }
2543
2544 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2545 let room = client.get_room(room_id).expect("Room not found!");
2546 room.direct_targets()
2547 }
2548
2549 async fn create_dm(
2552 client: &BaseClient,
2553 room_id: &RoomId,
2554 my_id: &UserId,
2555 their_id: &UserId,
2556 other_state: MembershipState,
2557 ) {
2558 let mut room = http::response::Room::new();
2559 set_room_joined(&mut room, my_id);
2560
2561 match other_state {
2562 MembershipState::Join => {
2563 room.joined_count = Some(uint!(2));
2564 room.invited_count = None;
2565 }
2566
2567 MembershipState::Invite => {
2568 room.joined_count = Some(uint!(1));
2569 room.invited_count = Some(uint!(1));
2570 }
2571
2572 _ => {
2573 room.joined_count = Some(uint!(1));
2574 room.invited_count = None;
2575 }
2576 }
2577
2578 room.required_state.push(make_membership_event(their_id, other_state));
2579
2580 let mut response = response_with_room(room_id, room);
2581 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2582 client
2583 .process_sliding_sync(
2584 &response,
2585 &RequestedRequiredStates::default(),
2586 &client.state_store_lock().lock().await,
2587 )
2588 .await
2589 .expect("Failed to process sync");
2590 }
2591
2592 async fn update_room_membership(
2594 client: &BaseClient,
2595 room_id: &RoomId,
2596 user_id: &UserId,
2597 new_state: MembershipState,
2598 ) {
2599 let mut room = http::response::Room::new();
2600 room.required_state.push(make_membership_event(user_id, new_state));
2601 let response = response_with_room(room_id, room);
2602 client
2603 .process_sliding_sync(
2604 &response,
2605 &RequestedRequiredStates::default(),
2606 &client.state_store_lock().lock().await,
2607 )
2608 .await
2609 .expect("Failed to process sync");
2610 }
2611
2612 fn set_direct_with(
2613 response: &mut http::Response,
2614 user_id: OwnedUserId,
2615 room_ids: Vec<OwnedRoomId>,
2616 ) {
2617 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2618 BTreeMap::new();
2619 direct_content.insert(user_id.into(), room_ids);
2620 response
2621 .extensions
2622 .account_data
2623 .global
2624 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2625 }
2626
2627 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2628 let mut response = http::Response::new("5".to_owned());
2629 response.rooms.insert(room_id.to_owned(), room);
2630 response
2631 }
2632
2633 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2634 let mut room = http::response::Room::new();
2635
2636 let mut avatar_event_content = RoomAvatarEventContent::new();
2637 avatar_event_content.url = Some(avatar_uri.to_owned());
2638
2639 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2640
2641 room
2642 }
2643
2644 fn room_with_canonical_alias(
2645 room_alias_id: &RoomAliasId,
2646 user_id: &UserId,
2647 ) -> http::response::Room {
2648 let mut room = http::response::Room::new();
2649
2650 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2651 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2652
2653 room.required_state.push(make_state_event(
2654 user_id,
2655 "",
2656 canonical_alias_event_content,
2657 None,
2658 ));
2659
2660 room
2661 }
2662
2663 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2664 let mut room = http::response::Room::new();
2665
2666 let name_event_content = RoomNameEventContent::new(name.to_owned());
2667
2668 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2669
2670 room
2671 }
2672
2673 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2674 room.required_state.push(make_state_event(
2675 sender,
2676 "",
2677 RoomNameEventContent::new(name),
2678 None,
2679 ));
2680 }
2681
2682 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2683 let evt = Raw::new(&json!({
2687 "type": "m.room.member",
2688 "sender": inviter,
2689 "content": {
2690 "is_direct": true,
2691 "membership": "invite",
2692 },
2693 "state_key": invitee,
2694 }))
2695 .expect("Failed to make raw event")
2696 .cast_unchecked();
2697
2698 room.invite_state = Some(vec![evt]);
2699
2700 room.required_state.push(make_state_event(
2703 inviter,
2704 invitee.as_str(),
2705 RoomMemberEventContent::new(MembershipState::Invite),
2706 None,
2707 ));
2708 }
2709
2710 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2711 let evt = Raw::new(&json!({
2715 "type": "m.room.member",
2716 "sender": knocker,
2717 "content": {
2718 "is_direct": true,
2719 "membership": "knock",
2720 },
2721 "state_key": knocker,
2722 }))
2723 .expect("Failed to make raw event")
2724 .cast_unchecked();
2725
2726 room.invite_state = Some(vec![evt]);
2727 }
2728
2729 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2730 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2731 }
2732
2733 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2734 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2735 }
2736
2737 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2738 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2739 }
2740
2741 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2742 room.required_state.push(make_encryption_event(user_id));
2743 }
2744
2745 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2746 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2747 }
2748
2749 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2750 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2751 }
2752
2753 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2754 Raw::new(&json!({
2755 "type": content.event_type(),
2756 "content": content,
2757 }))
2758 .expect("Failed to create account data event")
2759 .cast_unchecked()
2760 }
2761
2762 fn make_state_event<C: StateEventContent, E>(
2763 sender: &UserId,
2764 state_key: &str,
2765 content: C,
2766 prev_content: Option<C>,
2767 ) -> Raw<E> {
2768 let unsigned = if let Some(prev_content) = prev_content {
2769 json!({ "prev_content": prev_content })
2770 } else {
2771 json!({})
2772 };
2773
2774 Raw::new(&json!({
2775 "type": content.event_type(),
2776 "state_key": state_key,
2777 "content": content,
2778 "event_id": event_id!("$evt"),
2779 "sender": sender,
2780 "origin_server_ts": 10,
2781 "unsigned": unsigned,
2782 }))
2783 .expect("Failed to create state event")
2784 .cast_unchecked()
2785 }
2786}