1#[cfg(feature = "e2e-encryption")]
18use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
19use matrix_sdk_common::{deserialized_responses::TimelineEvent, timer};
20use ruma::{
21 OwnedRoomId, api::client::sync::sync_events::v5 as http, events::receipt::SyncReceiptEvent,
22 serde::Raw,
23};
24use tracing::{instrument, trace};
25
26use super::BaseClient;
27use crate::{
28 RequestedRequiredStates,
29 error::Result,
30 read_receipts::compute_unread_counts,
31 response_processors as processors,
32 room::RoomInfoNotableUpdateReasons,
33 store::ambiguity_map::AmbiguityCache,
34 sync::{RoomUpdates, SyncResponse},
35};
36
37impl BaseClient {
38 #[cfg(feature = "e2e-encryption")]
46 pub async fn process_sliding_sync_e2ee(
47 &self,
48 to_device: Option<&http::response::ToDevice>,
49 e2ee: &http::response::E2EE,
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 &self.ignore_user_list_changes,
81 None,
82 )
83 .await?;
84
85 Ok(Some(processed_to_device_events))
86 }
87
88 #[instrument(skip_all, level = "trace")]
95 pub async fn process_sliding_sync(
96 &self,
97 response: &http::Response,
98 requested_required_states: &RequestedRequiredStates,
99 ) -> Result<SyncResponse> {
100 let http::Response { rooms, lists, extensions, .. } = response;
101
102 trace!(
103 rooms = rooms.len(),
104 lists = lists.len(),
105 has_extensions = !extensions.is_empty(),
106 "Processing sliding sync room events"
107 );
108
109 if rooms.is_empty() && extensions.is_empty() {
110 return Ok(SyncResponse::default());
113 }
114
115 let _timer = timer!(tracing::Level::TRACE, "_method");
116
117 let mut context = processors::Context::default();
118
119 let state_store = self.state_store.clone();
120 let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
121
122 let global_account_data_processor =
123 processors::account_data::global(&extensions.account_data.global);
124 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
125
126 let mut room_updates = RoomUpdates::default();
127 let mut notifications = Default::default();
128
129 let user_id = self
130 .session_meta()
131 .expect("Sliding sync shouldn't run without an authenticated user.")
132 .user_id
133 .to_owned();
134
135 for (room_id, room_response) in rooms {
136 let Some((room_info, room_update)) = processors::room::msc4186::update_any_room(
137 &mut context,
138 &user_id,
139 processors::room::RoomCreationData::new(
140 room_id,
141 self.room_info_notable_update_sender.clone(),
142 requested_required_states,
143 &mut ambiguity_cache,
144 ),
145 room_response,
146 &extensions.account_data.rooms,
147 #[cfg(feature = "e2e-encryption")]
148 processors::e2ee::E2EE::new(
149 self.olm_machine().await.as_ref(),
150 &self.decryption_settings,
151 self.handle_verification_events,
152 ),
153 processors::notification::Notification::new(
154 &push_rules,
155 &mut notifications,
156 &self.state_store,
157 ),
158 )
159 .await?
160 else {
161 continue;
162 };
163
164 context.state_changes.add_room(room_info);
165
166 let room_id = room_id.to_owned();
167
168 use processors::room::msc4186::RoomUpdateKind;
169
170 match room_update {
171 RoomUpdateKind::Joined(joined_room_update) => {
172 room_updates.joined.insert(room_id, joined_room_update);
173 }
174 RoomUpdateKind::Left(left_room_update) => {
175 room_updates.left.insert(room_id, left_room_update);
176 }
177 RoomUpdateKind::Invited(invited_room_update) => {
178 room_updates.invited.insert(room_id, invited_room_update);
179 }
180 RoomUpdateKind::Knocked(knocked_room_update) => {
181 room_updates.knocked.insert(room_id, knocked_room_update);
182 }
183 }
184 }
185
186 processors::room::msc4186::extensions::dispatch_typing_ephemeral_events(
190 &extensions.typing,
191 &mut room_updates.joined,
192 );
193
194 processors::room::msc4186::extensions::room_account_data(
196 &mut context,
197 &extensions.account_data,
198 &mut room_updates,
199 &self.state_store,
200 );
201
202 global_account_data_processor.apply(&mut context, &state_store).await;
203
204 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
205
206 processors::changes::save_and_apply(
208 context,
209 &self.state_store,
210 &self.ignore_user_list_changes,
211 None,
212 )
213 .await?;
214
215 let mut context = processors::Context::default();
216
217 processors::room::display_name::update_for_rooms(
220 &mut context,
221 &room_updates,
222 &self.state_store,
223 )
224 .await;
225
226 processors::changes::save_only(context, &self.state_store).await?;
228
229 Ok(SyncResponse {
230 rooms: room_updates,
231 notifications,
232 presence: Default::default(),
233 account_data: extensions.account_data.global.clone(),
234 to_device: Default::default(),
235 })
236 }
237
238 #[doc(hidden)]
241 pub async fn process_sliding_sync_receipts_extension_for_room(
242 &self,
243 room_id: &OwnedRoomId,
244 response: &http::Response,
245 new_sync_events: Vec<TimelineEvent>,
246 room_previous_events: Vec<TimelineEvent>,
247 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
248 let mut context = processors::Context::default();
249
250 let mut save_context = false;
251
252 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
254 response.extensions.receipts.rooms.get(room_id)
255 {
256 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
257 &mut context,
258 room_id,
259 receipt_ephemeral_event,
260 );
261 save_context = true;
262 Some(receipt_ephemeral_event.clone())
263 } else {
264 None
265 };
266
267 let user_id = &self.session_meta().expect("logged in user").user_id;
268
269 if let Some(mut room_info) = self.get_room(room_id).map(|room| room.clone_info()) {
272 let prev_read_receipts = room_info.read_receipts.clone();
273
274 compute_unread_counts(
275 user_id,
276 room_id,
277 context.state_changes.receipts.get(room_id),
278 room_previous_events,
279 &new_sync_events,
280 &mut room_info.read_receipts,
281 self.threading_support,
282 );
283
284 if prev_read_receipts != room_info.read_receipts {
285 context
286 .room_info_notable_updates
287 .entry(room_id.clone())
288 .or_default()
289 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
290
291 context.state_changes.add_room(room_info);
292 save_context = true;
293 }
294 }
295
296 if save_context {
298 processors::changes::save_only(context, &self.state_store).await?;
299 }
300
301 Ok(receipt_ephemeral_event)
302 }
303}
304
305#[cfg(all(test, not(target_family = "wasm")))]
306mod tests {
307 use std::collections::{BTreeMap, HashSet};
308
309 use assert_matches::assert_matches;
310 use matrix_sdk_test::async_test;
311 use ruma::{
312 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
313 api::client::sync::sync_events::UnreadNotificationsCount,
314 assign, event_id,
315 events::{
316 GlobalAccountDataEventContent, StateEventContent, StateEventType,
317 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
318 room::{
319 avatar::RoomAvatarEventContent,
320 canonical_alias::RoomCanonicalAliasEventContent,
321 encryption::RoomEncryptionEventContent,
322 member::{MembershipState, RoomMemberEventContent},
323 name::RoomNameEventContent,
324 pinned_events::RoomPinnedEventsEventContent,
325 },
326 },
327 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
328 serde::Raw,
329 uint, user_id,
330 };
331 use serde_json::json;
332
333 use super::http;
334 use crate::{
335 BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
336 SessionMeta,
337 client::ThreadingSupport,
338 room::{RoomHero, RoomInfoNotableUpdateReasons},
339 store::{RoomLoadSettings, StoreConfig},
340 test_utils::logged_in_base_client,
341 };
342
343 #[async_test]
344 async fn test_notification_count_set() {
345 let client = logged_in_base_client(None).await;
346
347 let mut response = http::Response::new("42".to_owned());
348 let room_id = room_id!("!room:example.org");
349 let count = assign!(UnreadNotificationsCount::default(), {
350 highlight_count: Some(uint!(13)),
351 notification_count: Some(uint!(37)),
352 });
353
354 response.rooms.insert(
355 room_id.to_owned(),
356 assign!(http::response::Room::new(), {
357 unread_notifications: count.clone()
358 }),
359 );
360
361 let sync_response = client
362 .process_sliding_sync(&response, &RequestedRequiredStates::default())
363 .await
364 .expect("Failed to process sync");
365
366 let room = sync_response.rooms.joined.get(room_id).unwrap();
368 assert_eq!(room.unread_notifications, count.clone().into());
369
370 let room = client.get_room(room_id).expect("found room");
372 assert_eq!(room.unread_notification_counts(), count.into());
373 }
374
375 #[async_test]
376 async fn test_can_process_empty_sliding_sync_response() {
377 let client = logged_in_base_client(None).await;
378 let empty_response = http::Response::new("5".to_owned());
379 client
380 .process_sliding_sync(&empty_response, &RequestedRequiredStates::default())
381 .await
382 .expect("Failed to process sync");
383 }
384
385 #[async_test]
386 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
387 let client = logged_in_base_client(None).await;
389 let room_id = room_id!("!r:e.uk");
390
391 let mut room = http::response::Room::new();
394 room.joined_count = Some(uint!(41));
395 let response = response_with_room(room_id, room);
396 let sync_resp = client
397 .process_sliding_sync(&response, &RequestedRequiredStates::default())
398 .await
399 .expect("Failed to process sync");
400
401 let client_room = client.get_room(room_id).expect("No room found");
403 assert_eq!(client_room.room_id(), room_id);
404 assert_eq!(client_room.joined_members_count(), 41);
405 assert_eq!(client_room.state(), RoomState::Joined);
406
407 assert!(sync_resp.rooms.joined.contains_key(room_id));
409 assert!(!sync_resp.rooms.left.contains_key(room_id));
410 assert!(!sync_resp.rooms.invited.contains_key(room_id));
411 }
412
413 #[async_test]
414 async fn test_missing_room_name_event() {
415 let client = logged_in_base_client(None).await;
417 let room_id = room_id!("!r:e.uk");
418
419 let mut room = http::response::Room::new();
422 room.name = Some("little room".to_owned());
423 let response = response_with_room(room_id, room);
424 let sync_resp = client
425 .process_sliding_sync(&response, &RequestedRequiredStates::default())
426 .await
427 .expect("Failed to process sync");
428
429 let client_room = client.get_room(room_id).expect("No room found");
431 assert!(client_room.name().is_none());
432 assert_eq!(
433 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
434 "Empty Room"
435 );
436 assert_eq!(client_room.state(), RoomState::Joined);
437
438 assert!(sync_resp.rooms.joined.contains_key(room_id));
440 assert!(!sync_resp.rooms.left.contains_key(room_id));
441 assert!(!sync_resp.rooms.invited.contains_key(room_id));
442 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
443 }
444
445 #[async_test]
446 async fn test_room_name_event() {
447 let client = logged_in_base_client(None).await;
449 let room_id = room_id!("!r:e.uk");
450
451 let mut room = http::response::Room::new();
454
455 room.name = Some("little room".to_owned());
456 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
457
458 let response = response_with_room(room_id, room);
459 client
460 .process_sliding_sync(&response, &RequestedRequiredStates::default())
461 .await
462 .expect("Failed to process sync");
463
464 let client_room = client.get_room(room_id).expect("No room found");
466 assert_eq!(client_room.name().as_deref(), Some("The Name"));
467 assert_eq!(
468 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
469 "The Name"
470 );
471 }
472
473 #[async_test]
474 async fn test_missing_invited_room_name_event() {
475 let client = logged_in_base_client(None).await;
477 let room_id = room_id!("!r:e.uk");
478 let user_id = user_id!("@w:e.uk");
479 let inviter = user_id!("@john:mastodon.org");
480
481 let mut room = http::response::Room::new();
484 set_room_invited(&mut room, inviter, user_id);
485 room.name = Some("name from sliding sync response".to_owned());
486 let response = response_with_room(room_id, room);
487 let sync_resp = client
488 .process_sliding_sync(&response, &RequestedRequiredStates::default())
489 .await
490 .expect("Failed to process sync");
491
492 let client_room = client.get_room(room_id).expect("No room found");
494 assert!(client_room.name().is_none());
495
496 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
498
499 assert_eq!(client_room.state(), RoomState::Invited);
500
501 assert!(!sync_resp.rooms.joined.contains_key(room_id));
503 assert!(!sync_resp.rooms.left.contains_key(room_id));
504 assert!(sync_resp.rooms.invited.contains_key(room_id));
505 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
506 }
507
508 #[async_test]
509 async fn test_invited_room_name_event() {
510 let client = logged_in_base_client(None).await;
512 let room_id = room_id!("!r:e.uk");
513 let user_id = user_id!("@w:e.uk");
514 let inviter = user_id!("@john:mastodon.org");
515
516 let mut room = http::response::Room::new();
519
520 set_room_invited(&mut room, inviter, user_id);
521
522 room.name = Some("name from sliding sync response".to_owned());
523 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
524
525 let response = response_with_room(room_id, room);
526 client
527 .process_sliding_sync(&response, &RequestedRequiredStates::default())
528 .await
529 .expect("Failed to process sync");
530
531 let client_room = client.get_room(room_id).expect("No room found");
533 assert_eq!(client_room.name().as_deref(), Some("The Name"));
534 assert_eq!(
535 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
536 "The Name"
537 );
538 }
539
540 #[async_test]
541 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
542 let client = logged_in_base_client(None).await;
544 let room_id = room_id!("!r:e.uk");
545 let user_id = client.session_meta().unwrap().user_id.to_owned();
546
547 let mut room = http::response::Room::new();
550 set_room_knocked(&mut room, &user_id);
551
552 let response = response_with_room(room_id, room);
553 client
554 .process_sliding_sync(&response, &RequestedRequiredStates::default())
555 .await
556 .expect("Failed to process sync");
557
558 let client_room = client.get_room(room_id).expect("No room found");
560 assert_eq!(client_room.state(), RoomState::Knocked);
561 }
562
563 #[async_test]
564 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
565 {
566 let client = logged_in_base_client(None).await;
568 let room_id = room_id!("!r:e.uk");
569 let user_id = user_id!("@w:e.uk");
570
571 let mut room = http::response::Room::new();
573 set_room_knocked(&mut room, user_id);
574
575 let response = response_with_room(room_id, room);
576 client
577 .process_sliding_sync(&response, &RequestedRequiredStates::default())
578 .await
579 .expect("Failed to process sync");
580
581 let client_room = client.get_room(room_id).expect("No room found");
584 assert_eq!(client_room.state(), RoomState::Invited);
585 }
586
587 #[async_test]
588 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
589 {
590 let client = logged_in_base_client(None).await;
592 let room_id = room_id!("!r:e.uk");
593 let user_id = client.session_meta().unwrap().user_id.to_owned();
594
595 let mut room = http::response::Room::new();
597 let event = Raw::new(&json!({
598 "type": "m.room.member",
599 "sender": user_id,
600 "content": {
601 "is_direct": true,
602 "membership": "join",
603 },
604 "state_key": user_id,
605 }))
606 .expect("Failed to make raw event")
607 .cast_unchecked();
608 room.invite_state = Some(vec![event]);
609
610 let response = response_with_room(room_id, room);
611 client
612 .process_sliding_sync(&response, &RequestedRequiredStates::default())
613 .await
614 .expect("Failed to process sync");
615
616 let client_room = client.get_room(room_id).expect("No room found");
618 assert_eq!(client_room.state(), RoomState::Invited);
619 }
620
621 #[async_test]
622 async fn test_left_a_room_from_required_state_event() {
623 let client = logged_in_base_client(None).await;
625 let room_id = room_id!("!r:e.uk");
626 let user_id = user_id!("@u:e.uk");
627
628 let mut room = http::response::Room::new();
630 set_room_joined(&mut room, user_id);
631 let response = response_with_room(room_id, room);
632 client
633 .process_sliding_sync(&response, &RequestedRequiredStates::default())
634 .await
635 .expect("Failed to process sync");
636 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
637
638 let mut room = http::response::Room::new();
640 set_room_left(&mut room, user_id);
641 let response = response_with_room(room_id, room);
642 let sync_resp = client
643 .process_sliding_sync(&response, &RequestedRequiredStates::default())
644 .await
645 .expect("Failed to process sync");
646
647 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
649
650 assert!(!sync_resp.rooms.joined.contains_key(room_id));
652 assert!(sync_resp.rooms.left.contains_key(room_id));
653 assert!(!sync_resp.rooms.invited.contains_key(room_id));
654 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
655 }
656
657 #[async_test]
658 async fn test_kick_or_ban_updates_room_to_left() {
659 for membership in [MembershipState::Leave, MembershipState::Ban] {
660 let room_id = room_id!("!r:e.uk");
661 let user_a_id = user_id!("@a:e.uk");
662 let user_b_id = user_id!("@b:e.uk");
663 let client = logged_in_base_client(Some(user_a_id)).await;
664
665 let mut room = http::response::Room::new();
667 set_room_joined(&mut room, user_a_id);
668 let response = response_with_room(room_id, room);
669 client
670 .process_sliding_sync(&response, &RequestedRequiredStates::default())
671 .await
672 .expect("Failed to process sync");
673 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
674
675 let mut room = http::response::Room::new();
677 room.required_state.push(make_state_event(
678 user_b_id,
679 user_a_id.as_str(),
680 RoomMemberEventContent::new(membership.clone()),
681 None,
682 ));
683 let response = response_with_room(room_id, room);
684 let sync_resp = client
685 .process_sliding_sync(&response, &RequestedRequiredStates::default())
686 .await
687 .expect("Failed to process sync");
688
689 match membership {
690 MembershipState::Leave => {
691 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
693 }
694 MembershipState::Ban => {
695 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
697 }
698 _ => panic!("Unexpected membership state found: {membership}"),
699 }
700
701 assert!(!sync_resp.rooms.joined.contains_key(room_id));
703 assert!(sync_resp.rooms.left.contains_key(room_id));
704 assert!(!sync_resp.rooms.invited.contains_key(room_id));
705 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
706 }
707 }
708
709 #[async_test]
710 async fn test_left_a_room_from_timeline_state_event() {
711 let client = logged_in_base_client(None).await;
713 let room_id = room_id!("!r:e.uk");
714 let user_id = user_id!("@u:e.uk");
715
716 let mut room = http::response::Room::new();
718 set_room_joined(&mut room, user_id);
719 let response = response_with_room(room_id, room);
720 client
721 .process_sliding_sync(&response, &RequestedRequiredStates::default())
722 .await
723 .expect("Failed to process sync");
724 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
725
726 let mut room = http::response::Room::new();
728 set_room_left_as_timeline_event(&mut room, user_id);
729 let response = response_with_room(room_id, room);
730 client
731 .process_sliding_sync(&response, &RequestedRequiredStates::default())
732 .await
733 .expect("Failed to process sync");
734
735 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
737 }
738
739 #[async_test]
740 async fn test_can_be_reinvited_to_a_left_room() {
741 let client = logged_in_base_client(None).await;
745 let room_id = room_id!("!r:e.uk");
746 let user_id = user_id!("@u:e.uk");
747
748 let mut room = http::response::Room::new();
750 set_room_joined(&mut room, user_id);
751 let response = response_with_room(room_id, room);
752 client
753 .process_sliding_sync(&response, &RequestedRequiredStates::default())
754 .await
755 .expect("Failed to process sync");
756 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
758
759 let mut room = http::response::Room::new();
761 set_room_left(&mut room, user_id);
762 let response = response_with_room(room_id, room);
763 client
764 .process_sliding_sync(&response, &RequestedRequiredStates::default())
765 .await
766 .expect("Failed to process sync");
767 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
769
770 let mut room = http::response::Room::new();
772 set_room_invited(&mut room, user_id, user_id);
773 let response = response_with_room(room_id, room);
774 client
775 .process_sliding_sync(&response, &RequestedRequiredStates::default())
776 .await
777 .expect("Failed to process sync");
778
779 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
781 }
782
783 #[async_test]
784 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
785 let room_id = room_id!("!r:e.uk");
786 let user_a_id = user_id!("@a:e.uk");
787 let user_b_id = user_id!("@b:e.uk");
788
789 let client = logged_in_base_client(None).await;
791 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
792
793 assert!(
795 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
796 );
797 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
798
799 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
801
802 assert!(
806 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
807 );
808 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
809 }
810
811 #[async_test]
812 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
813 {
814 let room_id = room_id!("!r:e.uk");
815 let user_a_id = user_id!("@a:e.uk");
816 let user_b_id = user_id!("@b:e.uk");
817
818 let client = logged_in_base_client(None).await;
820 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
821
822 assert!(
824 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
825 );
826 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
827
828 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
830
831 assert!(
835 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
836 );
837 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
838 }
839
840 #[async_test]
841 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
842 let room_id = room_id!("!r:bar.org");
843 let user_a_id = user_id!("@a:bar.org");
844 let user_b_id = user_id!("@b:bar.org");
845
846 let client = logged_in_base_client(None).await;
848 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
849
850 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
852
853 assert!(
855 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
856 );
857 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
858
859 let room = client.get_room(room_id).unwrap();
860
861 assert_eq!(room.active_members_count(), 2);
862 assert_eq!(room.joined_members_count(), 2);
863 assert_eq!(room.invited_members_count(), 0);
864 }
865
866 #[async_test]
867 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
868 let room_id = room_id!("!r:bar.org");
869 let user_a_id = user_id!("@a:bar.org");
870 let user_b_id = user_id!("@b:bar.org");
871
872 let client = logged_in_base_client(None).await;
874 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
875
876 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
878
879 assert!(
881 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
882 );
883 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
884
885 let room = client.get_room(room_id).unwrap();
886
887 assert_eq!(room.active_members_count(), 2);
888 assert_eq!(room.joined_members_count(), 1);
889 assert_eq!(room.invited_members_count(), 1);
890 }
891
892 #[async_test]
893 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
894 let client = logged_in_base_client(None).await;
896 let room_id = room_id!("!r:e.uk");
897
898 let room = {
900 let mut room = http::response::Room::new();
901 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
902
903 room
904 };
905 let response = response_with_room(room_id, room);
906 client
907 .process_sliding_sync(&response, &RequestedRequiredStates::default())
908 .await
909 .expect("Failed to process sync");
910
911 let client_room = client.get_room(room_id).expect("No room found");
913 assert_eq!(
914 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
915 "med1"
916 );
917 }
918
919 #[async_test]
920 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
921 let client = logged_in_base_client(None).await;
923 let room_id = room_id!("!r:e.uk");
924
925 let room = {
929 let mut room = http::response::Room::new();
930 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
931
932 room
933 };
934 let response = response_with_room(room_id, room);
935 client
936 .process_sliding_sync(&response, &RequestedRequiredStates::default())
937 .await
938 .expect("Failed to process sync");
939
940 let client_room = client.get_room(room_id).expect("No room found");
942 assert_eq!(
943 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
944 "med1"
945 );
946
947 let room = http::response::Room::new();
951 let response = response_with_room(room_id, room);
952 client
953 .process_sliding_sync(&response, &RequestedRequiredStates::default())
954 .await
955 .expect("Failed to process sync");
956
957 let client_room = client.get_room(room_id).expect("No room found");
959 assert_eq!(
960 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
961 "med1"
962 );
963
964 let room = {
968 let mut room = http::response::Room::new();
969 room.avatar = JsOption::Null;
970
971 room
972 };
973 let response = response_with_room(room_id, room);
974 client
975 .process_sliding_sync(&response, &RequestedRequiredStates::default())
976 .await
977 .expect("Failed to process sync");
978
979 let client_room = client.get_room(room_id).expect("No room found");
981 assert!(client_room.avatar_url().is_none());
982 }
983
984 #[async_test]
985 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
986 let client = logged_in_base_client(None).await;
988 let room_id = room_id!("!r:e.uk");
989 let user_id = user_id!("@u:e.uk");
990
991 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
993 let response = response_with_room(room_id, room);
994 client
995 .process_sliding_sync(&response, &RequestedRequiredStates::default())
996 .await
997 .expect("Failed to process sync");
998
999 let client_room = client.get_room(room_id).expect("No room found");
1001 assert_eq!(
1002 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1003 "med1"
1004 );
1005 }
1006
1007 #[async_test]
1008 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1009 let client = logged_in_base_client(None).await;
1011 let room_id = room_id!("!r:e.uk");
1012 let user_id = user_id!("@u:e.uk");
1013
1014 let mut room = http::response::Room::new();
1016 set_room_invited(&mut room, user_id, user_id);
1017 let response = response_with_room(room_id, room);
1018 let sync_resp = client
1019 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1020 .await
1021 .expect("Failed to process sync");
1022
1023 let client_room = client.get_room(room_id).expect("No room found");
1025 assert_eq!(client_room.room_id(), room_id);
1026 assert_eq!(client_room.state(), RoomState::Invited);
1027
1028 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1030 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1031 }
1032
1033 #[async_test]
1034 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1035 let client = logged_in_base_client(None).await;
1037 let room_id = room_id!("!r:e.uk");
1038 let user_id = user_id!("@u:e.uk");
1039
1040 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1042 set_room_invited(&mut room, user_id, user_id);
1043 let response = response_with_room(room_id, room);
1044 client
1045 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1046 .await
1047 .expect("Failed to process sync");
1048
1049 let client_room = client.get_room(room_id).expect("No room found");
1051 assert_eq!(
1052 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1053 "med1"
1054 );
1055 }
1056
1057 #[async_test]
1058 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1059 {
1060 let client = logged_in_base_client(None).await;
1062 let room_id = room_id!("!r:e.uk");
1063 let user_id = user_id!("@u:e.uk");
1064 let room_alias_id = room_alias_id!("#myroom:e.uk");
1065
1066 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1068 set_room_invited(&mut room, user_id, user_id);
1069 let response = response_with_room(room_id, room);
1070 client
1071 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1072 .await
1073 .expect("Failed to process sync");
1074
1075 let client_room = client.get_room(room_id).expect("No room found");
1077 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1078 }
1079
1080 #[async_test]
1081 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1082 let client = logged_in_base_client(None).await;
1084 let room_id = room_id!("!r:e.uk");
1085 let user_id = user_id!("@u:e.uk");
1086 let room_alias_id = room_alias_id!("#myroom:e.uk");
1087
1088 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1091 room.name = Some("This came from the server".to_owned());
1092 let response = response_with_room(room_id, room);
1093 client
1094 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1095 .await
1096 .expect("Failed to process sync");
1097
1098 let client_room = client.get_room(room_id).expect("No room found");
1100 assert_eq!(
1101 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1102 "myroom"
1103 );
1104 assert!(client_room.name().is_none());
1105 }
1106
1107 #[async_test]
1108 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1109 let client = logged_in_base_client(None).await;
1110 let user_id = user_id!("@u:e.uk");
1111 let room_id = room_id!("!r:e.uk");
1112
1113 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1114
1115 let room = room_with_name("Hello World", user_id);
1116 let response = response_with_room(room_id, room);
1117 client
1118 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1119 .await
1120 .expect("Failed to process sync");
1121
1122 let room = client.get_room(room_id).expect("No room found");
1123 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1124
1125 assert_matches!(
1126 room_info_notable_update.recv().await,
1127 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1128 assert_eq!(received_room_id, room_id);
1129 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1130 }
1131 );
1132 assert_matches!(
1133 room_info_notable_update.recv().await,
1134 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1135 assert_eq!(received_room_id, room_id);
1136 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1138 }
1139 );
1140 assert!(room_info_notable_update.is_empty());
1141 }
1142
1143 #[async_test]
1144 async fn test_display_name_is_persisted_from_sliding_sync() {
1145 let user_id = user_id!("@u:e.uk");
1146 let room_id = room_id!("!r:e.uk");
1147 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1148 let state_store;
1149
1150 {
1151 let client = {
1152 let store = StoreConfig::new("cross-process-foo".to_owned());
1153 state_store = store.state_store.clone();
1154
1155 let client = BaseClient::new(store, ThreadingSupport::Disabled);
1156 client
1157 .activate(
1158 session_meta.clone(),
1159 RoomLoadSettings::default(),
1160 #[cfg(feature = "e2e-encryption")]
1161 None,
1162 )
1163 .await
1164 .expect("`activate` failed!");
1165
1166 client
1167 };
1168
1169 let room = room_with_name("Hello World", user_id);
1172 let response = response_with_room(room_id, room);
1173 client
1174 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1175 .await
1176 .expect("Failed to process sync");
1177
1178 let room = client.get_room(room_id).expect("No room found");
1179 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1180 }
1181
1182 {
1183 let client = {
1184 let mut store = StoreConfig::new("cross-process-foo".to_owned());
1185 store.state_store = state_store;
1186 let client = BaseClient::new(store, ThreadingSupport::Disabled);
1187 client
1188 .activate(
1189 session_meta,
1190 RoomLoadSettings::default(),
1191 #[cfg(feature = "e2e-encryption")]
1192 None,
1193 )
1194 .await
1195 .expect("`activate` failed!");
1196
1197 client
1198 };
1199
1200 let room = client.get_room(room_id).expect("No room found");
1201 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1202 }
1203 }
1204
1205 #[async_test]
1206 async fn test_compute_heroes_from_sliding_sync() {
1207 let client = logged_in_base_client(None).await;
1209 let room_id = room_id!("!r:e.uk");
1210 let gordon = user_id!("@gordon:e.uk").to_owned();
1211 let alice = user_id!("@alice:e.uk").to_owned();
1212
1213 let mut room = http::response::Room::new();
1216 room.heroes = Some(vec![
1217 assign!(http::response::Hero::new(gordon), {
1218 name: Some("Gordon".to_owned()),
1219 }),
1220 assign!(http::response::Hero::new(alice), {
1221 name: Some("Alice".to_owned()),
1222 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1223 }),
1224 ]);
1225 let response = response_with_room(room_id, room);
1226 let _sync_resp = client
1227 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1228 .await
1229 .expect("Failed to process sync");
1230
1231 let client_room = client.get_room(room_id).expect("No room found");
1233 assert_eq!(client_room.room_id(), room_id);
1234 assert_eq!(client_room.state(), RoomState::Joined);
1235
1236 assert_eq!(
1238 client_room.clone_info().summary.heroes(),
1239 &[
1240 RoomHero {
1241 user_id: owned_user_id!("@gordon:e.uk"),
1242 display_name: Some("Gordon".to_owned()),
1243 avatar_url: None
1244 },
1245 RoomHero {
1246 user_id: owned_user_id!("@alice:e.uk"),
1247 display_name: Some("Alice".to_owned()),
1248 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1249 },
1250 ]
1251 );
1252 }
1253
1254 #[async_test]
1255 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1256 let client = logged_in_base_client(None).await;
1258 let room_id = room_id!("!r:e.uk");
1259
1260 let room = assign!(http::response::Room::new(), {
1262 bump_stamp: Some(42u32.into()),
1263 });
1264 let response = response_with_room(room_id, room);
1265 client
1266 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1267 .await
1268 .expect("Failed to process sync");
1269
1270 let client_room = client.get_room(room_id).expect("No room found");
1272 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1273 }
1274
1275 #[async_test]
1276 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1277 let client = logged_in_base_client(None).await;
1279 let room_id = room_id!("!r:e.uk");
1280
1281 {
1282 let room = assign!(http::response::Room::new(), {
1284 bump_stamp: Some(42u32.into()),
1285 });
1286 let response = response_with_room(room_id, room);
1287 client
1288 .process_sliding_sync(&response, &RequestedRequiredStates::default())
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!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1295 }
1296
1297 {
1298 let room = assign!(http::response::Room::new(), {
1300 bump_stamp: None,
1301 });
1302 let response = response_with_room(room_id, room);
1303 client
1304 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1305 .await
1306 .expect("Failed to process sync");
1307
1308 let client_room = client.get_room(room_id).expect("No room found");
1310 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
1311 }
1312
1313 {
1314 let room = assign!(http::response::Room::new(), {
1317 bump_stamp: Some(153u32.into()),
1318 });
1319 let response = response_with_room(room_id, room);
1320 client
1321 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1322 .await
1323 .expect("Failed to process sync");
1324
1325 let client_room = client.get_room(room_id).expect("No room found");
1327 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
1328 }
1329 }
1330
1331 #[async_test]
1332 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1333 let client = logged_in_base_client(None).await;
1335 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1336 let room_id = room_id!("!r:e.uk");
1337
1338 let room = assign!(http::response::Room::new(), {
1340 bump_stamp: Some(42u32.into()),
1341 });
1342 let response = response_with_room(room_id, room);
1343 client
1344 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1345 .await
1346 .expect("Failed to process sync");
1347
1348 assert_matches!(
1351 room_info_notable_update_stream.recv().await,
1352 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1353 assert_eq!(received_room_id, room_id);
1354 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1355 }
1356 );
1357 assert_matches!(
1358 room_info_notable_update_stream.recv().await,
1359 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1360 assert_eq!(received_room_id, room_id);
1361 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1362 }
1363 );
1364 assert!(room_info_notable_update_stream.is_empty());
1365
1366 let room = assign!(http::response::Room::new(), {
1368 bump_stamp: Some(43u32.into()),
1369 });
1370 let response = response_with_room(room_id, room);
1371 client
1372 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1373 .await
1374 .expect("Failed to process sync");
1375
1376 assert_matches!(
1378 room_info_notable_update_stream.recv().await,
1379 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1380 assert_eq!(received_room_id, room_id);
1381 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1382 }
1383 );
1384 assert!(room_info_notable_update_stream.is_empty());
1385 }
1386
1387 #[async_test]
1388 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1389 let client = logged_in_base_client(None).await;
1391 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1392
1393 let room_id = room_id!("!r:e.uk");
1395 let room = http::response::Room::new();
1396 let response = response_with_room(room_id, room);
1397 client
1398 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1399 .await
1400 .expect("Failed to process sync");
1401
1402 assert_matches!(
1404 room_info_notable_update_stream.recv().await,
1405 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1406 assert_eq!(received_room_id, room_id);
1407 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1408 }
1409 );
1410 assert_matches!(
1411 room_info_notable_update_stream.recv().await,
1412 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1413 assert_eq!(received_room_id, room_id);
1414 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1415 }
1416 );
1417
1418 let room_id = room_id!("!r:e.uk");
1420 let events = vec![
1421 Raw::from_json_string(
1422 json!({
1423 "type": "m.room.member",
1424 "event_id": "$3",
1425 "content": { "membership": "join" },
1426 "sender": "@u:h.uk",
1427 "origin_server_ts": 12344445,
1428 "state_key": "@u:e.uk",
1429 })
1430 .to_string(),
1431 )
1432 .unwrap(),
1433 ];
1434 let room = assign!(http::response::Room::new(), {
1435 required_state: events,
1436 });
1437 let response = response_with_room(room_id, room);
1438 client
1439 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1440 .await
1441 .expect("Failed to process sync");
1442
1443 assert_matches!(
1445 room_info_notable_update_stream.recv().await,
1446 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1447 assert_eq!(received_room_id, room_id);
1448 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1449 }
1450 );
1451 assert!(room_info_notable_update_stream.is_empty());
1452
1453 let events = vec![
1454 Raw::from_json_string(
1455 json!({
1456 "type": "m.room.member",
1457 "event_id": "$3",
1458 "content": { "membership": "leave" },
1459 "sender": "@u:h.uk",
1460 "origin_server_ts": 12344445,
1461 "state_key": "@u:e.uk",
1462 })
1463 .to_string(),
1464 )
1465 .unwrap(),
1466 ];
1467 let room = assign!(http::response::Room::new(), {
1468 required_state: events,
1469 });
1470 let response = response_with_room(room_id, room);
1471 client
1472 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1473 .await
1474 .expect("Failed to process sync");
1475
1476 assert_matches!(
1478 room_info_notable_update_stream.recv().await,
1479 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1480 assert_eq!(received_room_id, room_id);
1481 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1482 }
1483 );
1484 assert!(room_info_notable_update_stream.is_empty());
1485 }
1486
1487 #[async_test]
1488 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1489 let client = logged_in_base_client(None).await;
1491 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1492
1493 let room_id = room_id!("!r:e.uk");
1495 let room = http::response::Room::new();
1496 let response = response_with_room(room_id, room);
1497 client
1498 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1499 .await
1500 .expect("Failed to process sync");
1501
1502 assert_matches!(
1504 room_info_notable_update_stream.recv().await,
1505 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1506 assert_eq!(received_room_id, room_id);
1507 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1508 }
1509 );
1510 assert_matches!(
1511 room_info_notable_update_stream.recv().await,
1512 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1513 assert_eq!(received_room_id, room_id);
1514 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
1515 }
1516 );
1517 assert!(room_info_notable_update_stream.is_empty());
1518
1519 let room_id = room_id!("!r:e.uk");
1522 let room_account_data_events = vec![
1523 Raw::from_json_string(
1524 json!({
1525 "type": "m.marked_unread",
1526 "event_id": "$1",
1527 "content": { "unread": true },
1528 "sender": client.session_meta().unwrap().user_id,
1529 "origin_server_ts": 12344445,
1530 })
1531 .to_string(),
1532 )
1533 .unwrap(),
1534 ];
1535 let mut response = response_with_room(room_id, http::response::Room::new());
1536 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1537
1538 client
1539 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1540 .await
1541 .expect("Failed to process sync");
1542
1543 assert_matches!(
1545 room_info_notable_update_stream.recv().await,
1546 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1547 assert_eq!(received_room_id, room_id);
1548 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
1549 }
1550 );
1551
1552 client
1554 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1555 .await
1556 .expect("Failed to process sync");
1557
1558 assert_matches!(
1559 room_info_notable_update_stream.recv().await,
1560 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1561 assert_eq!(received_room_id, room_id);
1562 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1563 }
1564 );
1565 assert!(room_info_notable_update_stream.is_empty());
1566
1567 let room_account_data_events = vec![
1569 Raw::from_json_string(
1570 json!({
1571 "type": "m.marked_unread",
1572 "event_id": "$1",
1573 "content": { "unread": false },
1574 "sender": client.session_meta().unwrap().user_id,
1575 "origin_server_ts": 12344445,
1576 })
1577 .to_string(),
1578 )
1579 .unwrap(),
1580 ];
1581 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
1582 client
1583 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1584 .await
1585 .expect("Failed to process sync");
1586
1587 assert_matches!(
1588 room_info_notable_update_stream.recv().await,
1589 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1590 assert_eq!(received_room_id, room_id);
1591 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
1592 }
1593 );
1594 assert!(room_info_notable_update_stream.is_empty());
1595 }
1596
1597 #[async_test]
1598 async fn test_unstable_unread_marker_is_ignored_after_stable() {
1599 let client = logged_in_base_client(None).await;
1601 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1602
1603 let room_id = room_id!("!r:e.uk");
1605 let room = http::response::Room::new();
1606 let response = response_with_room(room_id, room);
1607 client
1608 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1609 .await
1610 .expect("Failed to process sync");
1611
1612 assert_matches!(
1614 room_info_notable_update_stream.recv().await,
1615 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1616 assert_eq!(received_room_id, room_id);
1617 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1618 }
1619 );
1620 assert_matches!(
1621 room_info_notable_update_stream.recv().await,
1622 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1623 assert_eq!(received_room_id, room_id);
1624 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
1625 }
1626 );
1627 assert!(room_info_notable_update_stream.is_empty());
1628
1629 let room_id = room_id!("!r:e.uk");
1632 let unstable_room_account_data_events = vec![
1633 Raw::from_json_string(
1634 json!({
1635 "type": "com.famedly.marked_unread",
1636 "event_id": "$1",
1637 "content": { "unread": true },
1638 "sender": client.session_meta().unwrap().user_id,
1639 "origin_server_ts": 12344445,
1640 })
1641 .to_string(),
1642 )
1643 .unwrap(),
1644 ];
1645 let mut response = response_with_room(room_id, http::response::Room::new());
1646 response
1647 .extensions
1648 .account_data
1649 .rooms
1650 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
1651
1652 client
1653 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1654 .await
1655 .expect("Failed to process sync");
1656
1657 assert_matches!(
1659 room_info_notable_update_stream.recv().await,
1660 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1661 assert_eq!(received_room_id, room_id);
1662 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
1663 }
1664 );
1665 assert!(room_info_notable_update_stream.is_empty());
1666
1667 let stable_room_account_data_events = vec![
1669 Raw::from_json_string(
1670 json!({
1671 "type": "m.marked_unread",
1672 "event_id": "$1",
1673 "content": { "unread": false },
1674 "sender": client.session_meta().unwrap().user_id,
1675 "origin_server_ts": 12344445,
1676 })
1677 .to_string(),
1678 )
1679 .unwrap(),
1680 ];
1681 response
1682 .extensions
1683 .account_data
1684 .rooms
1685 .insert(room_id.to_owned(), stable_room_account_data_events);
1686 client
1687 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1688 .await
1689 .expect("Failed to process sync");
1690
1691 assert_matches!(
1693 room_info_notable_update_stream.recv().await,
1694 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1695 assert_eq!(received_room_id, room_id);
1696 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
1697 }
1698 );
1699 assert!(room_info_notable_update_stream.is_empty());
1700
1701 response
1704 .extensions
1705 .account_data
1706 .rooms
1707 .insert(room_id.to_owned(), unstable_room_account_data_events);
1708 client
1709 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1710 .await
1711 .expect("Failed to process sync");
1712
1713 assert_matches!(
1715 room_info_notable_update_stream.recv().await,
1716 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1717 assert_eq!(received_room_id, room_id);
1718 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1719 }
1720 );
1721 assert!(room_info_notable_update_stream.is_empty());
1722
1723 let stable_room_account_data_events = vec![
1726 Raw::from_json_string(
1727 json!({
1728 "type": "m.marked_unread",
1729 "event_id": "$3",
1730 "content": { "unread": true },
1731 "sender": client.session_meta().unwrap().user_id,
1732 "origin_server_ts": 12344445,
1733 })
1734 .to_string(),
1735 )
1736 .unwrap(),
1737 ];
1738 response
1739 .extensions
1740 .account_data
1741 .rooms
1742 .insert(room_id.to_owned(), stable_room_account_data_events);
1743 client
1744 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1745 .await
1746 .expect("Failed to process sync");
1747
1748 assert_matches!(
1750 room_info_notable_update_stream.recv().await,
1751 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1752 assert_eq!(received_room_id, room_id);
1753 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
1754 }
1755 );
1756 assert!(room_info_notable_update_stream.is_empty());
1757 }
1758
1759 #[async_test]
1760 async fn test_pinned_events_are_updated_on_sync() {
1761 let user_a_id = user_id!("@a:e.uk");
1762 let client = logged_in_base_client(Some(user_a_id)).await;
1763 let room_id = room_id!("!r:e.uk");
1764 let pinned_event_id = owned_event_id!("$an-id:e.uk");
1765
1766 let mut room_response = http::response::Room::new();
1768 set_room_joined(&mut room_response, user_a_id);
1769 let response = response_with_room(room_id, room_response);
1770 client
1771 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1772 .await
1773 .expect("Failed to process sync");
1774
1775 let room = client.get_room(room_id).unwrap();
1777 let pinned_event_ids = room.pinned_event_ids();
1778 assert_matches!(pinned_event_ids, None);
1779
1780 let mut room_response = http::response::Room::new();
1782 room_response.required_state.push(make_state_event(
1783 user_a_id,
1784 "",
1785 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
1786 None,
1787 ));
1788 let response = response_with_room(room_id, room_response);
1789 client
1790 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1791 .await
1792 .expect("Failed to process sync");
1793
1794 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
1795 assert_eq!(pinned_event_ids.len(), 1);
1796 assert_eq!(pinned_event_ids[0], pinned_event_id);
1797
1798 let mut room_response = http::response::Room::new();
1800 room_response.required_state.push(make_state_event(
1801 user_a_id,
1802 "",
1803 RoomPinnedEventsEventContent::new(Vec::new()),
1804 None,
1805 ));
1806 let response = response_with_room(room_id, room_response);
1807 client
1808 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1809 .await
1810 .expect("Failed to process sync");
1811 let pinned_event_ids = room.pinned_event_ids().unwrap();
1812 assert!(pinned_event_ids.is_empty());
1813 }
1814
1815 #[async_test]
1816 async fn test_dms_are_processed_in_any_sync_response() {
1817 let current_user_id = user_id!("@current:e.uk");
1818 let client = logged_in_base_client(Some(current_user_id)).await;
1819 let user_a_id = user_id!("@a:e.uk");
1820 let user_b_id = user_id!("@b:e.uk");
1821 let room_id_1 = room_id!("!r:e.uk");
1822 let room_id_2 = room_id!("!s:e.uk");
1823
1824 let mut room_response = http::response::Room::new();
1825 set_room_joined(&mut room_response, user_a_id);
1826 let mut response = response_with_room(room_id_1, room_response);
1827 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
1828 BTreeMap::new();
1829 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
1830 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
1831 response
1832 .extensions
1833 .account_data
1834 .global
1835 .push(make_global_account_data_event(DirectEventContent(direct_content)));
1836 client
1837 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1838 .await
1839 .expect("Failed to process sync");
1840
1841 let room_1 = client.get_room(room_id_1).unwrap();
1842 assert!(room_1.is_direct().await.unwrap());
1843
1844 let mut room_response = http::response::Room::new();
1846 set_room_joined(&mut room_response, user_b_id);
1847 let response = response_with_room(room_id_2, room_response);
1848 client
1849 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1850 .await
1851 .expect("Failed to process sync");
1852
1853 let room_2 = client.get_room(room_id_2).unwrap();
1854 assert!(room_2.is_direct().await.unwrap());
1855 }
1856
1857 #[async_test]
1858 async fn test_room_encryption_state_is_and_is_not_encrypted() {
1859 let user_id = user_id!("@raclette:patate");
1860 let client = logged_in_base_client(Some(user_id)).await;
1861 let room_id_0 = room_id!("!r0");
1862 let room_id_1 = room_id!("!r1");
1863 let room_id_2 = room_id!("!r2");
1864
1865 let requested_required_states = RequestedRequiredStates::from(&{
1882 let mut request = http::Request::new();
1883
1884 request.room_subscriptions.insert(room_id_0.to_owned(), {
1885 let mut room_subscription = http::request::RoomSubscription::default();
1886
1887 room_subscription
1888 .required_state
1889 .push((StateEventType::RoomEncryption, "".to_owned()));
1890
1891 room_subscription
1892 });
1893
1894 request
1895 });
1896
1897 let mut response = http::Response::new("0".to_owned());
1898
1899 {
1903 let not_encrypted_room = http::response::Room::new();
1904 let mut encrypted_room = http::response::Room::new();
1905 set_room_is_encrypted(&mut encrypted_room, user_id);
1906
1907 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
1908 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
1909 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
1910 }
1911
1912 client
1913 .process_sliding_sync(&response, &requested_required_states)
1914 .await
1915 .expect("Failed to process sync");
1916
1917 assert_matches!(
1919 client.get_room(room_id_0).unwrap().encryption_state(),
1920 EncryptionState::Encrypted
1921 );
1922 assert_matches!(
1923 client.get_room(room_id_1).unwrap().encryption_state(),
1924 EncryptionState::Encrypted
1925 );
1926 assert_matches!(
1928 client.get_room(room_id_2).unwrap().encryption_state(),
1929 EncryptionState::NotEncrypted
1930 )
1931 }
1932
1933 #[async_test]
1934 async fn test_room_encryption_state_is_unknown() {
1935 let user_id = user_id!("@raclette:patate");
1936 let client = logged_in_base_client(Some(user_id)).await;
1937 let room_id_0 = room_id!("!r0");
1938 let room_id_1 = room_id!("!r1");
1939
1940 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
1953
1954 let mut response = http::Response::new("0".to_owned());
1955
1956 {
1958 let not_encrypted_room = http::response::Room::new();
1959 let mut encrypted_room = http::response::Room::new();
1960 set_room_is_encrypted(&mut encrypted_room, user_id);
1961
1962 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
1963 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
1964 }
1965
1966 client
1967 .process_sliding_sync(&response, &requested_required_states)
1968 .await
1969 .expect("Failed to process sync");
1970
1971 assert_matches!(
1974 client.get_room(room_id_0).unwrap().encryption_state(),
1975 EncryptionState::Encrypted
1976 );
1977 assert_matches!(
1980 client.get_room(room_id_1).unwrap().encryption_state(),
1981 EncryptionState::Unknown
1982 );
1983 }
1984
1985 async fn membership(
1986 client: &BaseClient,
1987 room_id: &RoomId,
1988 user_id: &UserId,
1989 ) -> MembershipState {
1990 let room = client.get_room(room_id).expect("Room not found!");
1991 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
1992 member.membership().clone()
1993 }
1994
1995 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
1996 let room = client.get_room(room_id).expect("Room not found!");
1997 room.direct_targets()
1998 }
1999
2000 async fn create_dm(
2003 client: &BaseClient,
2004 room_id: &RoomId,
2005 my_id: &UserId,
2006 their_id: &UserId,
2007 other_state: MembershipState,
2008 ) {
2009 let mut room = http::response::Room::new();
2010 set_room_joined(&mut room, my_id);
2011
2012 match other_state {
2013 MembershipState::Join => {
2014 room.joined_count = Some(uint!(2));
2015 room.invited_count = None;
2016 }
2017
2018 MembershipState::Invite => {
2019 room.joined_count = Some(uint!(1));
2020 room.invited_count = Some(uint!(1));
2021 }
2022
2023 _ => {
2024 room.joined_count = Some(uint!(1));
2025 room.invited_count = None;
2026 }
2027 }
2028
2029 room.required_state.push(make_membership_event(their_id, other_state));
2030
2031 let mut response = response_with_room(room_id, room);
2032 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2033 client
2034 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2035 .await
2036 .expect("Failed to process sync");
2037 }
2038
2039 async fn update_room_membership(
2041 client: &BaseClient,
2042 room_id: &RoomId,
2043 user_id: &UserId,
2044 new_state: MembershipState,
2045 ) {
2046 let mut room = http::response::Room::new();
2047 room.required_state.push(make_membership_event(user_id, new_state));
2048 let response = response_with_room(room_id, room);
2049 client
2050 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2051 .await
2052 .expect("Failed to process sync");
2053 }
2054
2055 fn set_direct_with(
2056 response: &mut http::Response,
2057 user_id: OwnedUserId,
2058 room_ids: Vec<OwnedRoomId>,
2059 ) {
2060 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2061 BTreeMap::new();
2062 direct_content.insert(user_id.into(), room_ids);
2063 response
2064 .extensions
2065 .account_data
2066 .global
2067 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2068 }
2069
2070 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2071 let mut response = http::Response::new("5".to_owned());
2072 response.rooms.insert(room_id.to_owned(), room);
2073 response
2074 }
2075
2076 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2077 let mut room = http::response::Room::new();
2078
2079 let mut avatar_event_content = RoomAvatarEventContent::new();
2080 avatar_event_content.url = Some(avatar_uri.to_owned());
2081
2082 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2083
2084 room
2085 }
2086
2087 fn room_with_canonical_alias(
2088 room_alias_id: &RoomAliasId,
2089 user_id: &UserId,
2090 ) -> http::response::Room {
2091 let mut room = http::response::Room::new();
2092
2093 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2094 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2095
2096 room.required_state.push(make_state_event(
2097 user_id,
2098 "",
2099 canonical_alias_event_content,
2100 None,
2101 ));
2102
2103 room
2104 }
2105
2106 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2107 let mut room = http::response::Room::new();
2108
2109 let name_event_content = RoomNameEventContent::new(name.to_owned());
2110
2111 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2112
2113 room
2114 }
2115
2116 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2117 room.required_state.push(make_state_event(
2118 sender,
2119 "",
2120 RoomNameEventContent::new(name),
2121 None,
2122 ));
2123 }
2124
2125 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2126 let evt = Raw::new(&json!({
2130 "type": "m.room.member",
2131 "sender": inviter,
2132 "content": {
2133 "is_direct": true,
2134 "membership": "invite",
2135 },
2136 "state_key": invitee,
2137 }))
2138 .expect("Failed to make raw event")
2139 .cast_unchecked();
2140
2141 room.invite_state = Some(vec![evt]);
2142
2143 room.required_state.push(make_state_event(
2146 inviter,
2147 invitee.as_str(),
2148 RoomMemberEventContent::new(MembershipState::Invite),
2149 None,
2150 ));
2151 }
2152
2153 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2154 let evt = Raw::new(&json!({
2158 "type": "m.room.member",
2159 "sender": knocker,
2160 "content": {
2161 "is_direct": true,
2162 "membership": "knock",
2163 },
2164 "state_key": knocker,
2165 }))
2166 .expect("Failed to make raw event")
2167 .cast_unchecked();
2168
2169 room.invite_state = Some(vec![evt]);
2170 }
2171
2172 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2173 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2174 }
2175
2176 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2177 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2178 }
2179
2180 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2181 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2182 }
2183
2184 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2185 room.required_state.push(make_encryption_event(user_id));
2186 }
2187
2188 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2189 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2190 }
2191
2192 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2193 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2194 }
2195
2196 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2197 Raw::new(&json!({
2198 "type": content.event_type(),
2199 "content": content,
2200 }))
2201 .expect("Failed to create account data event")
2202 .cast_unchecked()
2203 }
2204
2205 fn make_state_event<C: StateEventContent, E>(
2206 sender: &UserId,
2207 state_key: &str,
2208 content: C,
2209 prev_content: Option<C>,
2210 ) -> Raw<E> {
2211 let unsigned = if let Some(prev_content) = prev_content {
2212 json!({ "prev_content": prev_content })
2213 } else {
2214 json!({})
2215 };
2216
2217 Raw::new(&json!({
2218 "type": content.event_type(),
2219 "state_key": state_key,
2220 "content": content,
2221 "event_id": event_id!("$evt"),
2222 "sender": sender,
2223 "origin_server_ts": 10,
2224 "unsigned": unsigned,
2225 }))
2226 .expect("Failed to create state event")
2227 .cast_unchecked()
2228 }
2229}