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 mut context = processors::Context::default();
67
68 let processors::e2ee::to_device::Output { processed_to_device_events, room_key_updates } =
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::latest_event::decrypt_from_rooms(
78 &mut context,
79 room_key_updates
80 .into_iter()
81 .flatten()
82 .filter_map(|room_key_info| self.get_room(&room_key_info.room_id))
83 .collect(),
84 processors::e2ee::E2EE::new(
85 olm_machine.as_ref(),
86 &self.decryption_settings,
87 self.handle_verification_events,
88 ),
89 )
90 .await?;
91
92 processors::changes::save_and_apply(
93 context,
94 &self.state_store,
95 &self.ignore_user_list_changes,
96 None,
97 )
98 .await?;
99
100 Ok(Some(processed_to_device_events))
101 }
102
103 #[instrument(skip_all, level = "trace")]
110 pub async fn process_sliding_sync(
111 &self,
112 response: &http::Response,
113 requested_required_states: &RequestedRequiredStates,
114 ) -> Result<SyncResponse> {
115 let http::Response { rooms, lists, extensions, .. } = response;
116
117 trace!(
118 rooms = rooms.len(),
119 lists = lists.len(),
120 has_extensions = !extensions.is_empty(),
121 "Processing sliding sync room events"
122 );
123
124 if rooms.is_empty() && extensions.is_empty() {
125 return Ok(SyncResponse::default());
128 }
129
130 let _timer = timer!(tracing::Level::TRACE, "_method");
131
132 let mut context = processors::Context::default();
133
134 let state_store = self.state_store.clone();
135 let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
136
137 let global_account_data_processor =
138 processors::account_data::global(&extensions.account_data.global);
139 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
140
141 let mut room_updates = RoomUpdates::default();
142 let mut notifications = Default::default();
143
144 let user_id = self
145 .session_meta()
146 .expect("Sliding sync shouldn't run without an authenticated user.")
147 .user_id
148 .to_owned();
149
150 for (room_id, room_response) in rooms {
151 let Some((room_info, room_update)) = processors::room::msc4186::update_any_room(
152 &mut context,
153 &user_id,
154 processors::room::RoomCreationData::new(
155 room_id,
156 self.room_info_notable_update_sender.clone(),
157 requested_required_states,
158 &mut ambiguity_cache,
159 ),
160 room_response,
161 &extensions.account_data.rooms,
162 #[cfg(feature = "e2e-encryption")]
163 processors::e2ee::E2EE::new(
164 self.olm_machine().await.as_ref(),
165 &self.decryption_settings,
166 self.handle_verification_events,
167 ),
168 processors::notification::Notification::new(
169 &push_rules,
170 &mut notifications,
171 &self.state_store,
172 ),
173 )
174 .await?
175 else {
176 continue;
177 };
178
179 context.state_changes.add_room(room_info);
180
181 let room_id = room_id.to_owned();
182
183 use processors::room::msc4186::RoomUpdateKind;
184
185 match room_update {
186 RoomUpdateKind::Joined(joined_room_update) => {
187 room_updates.joined.insert(room_id, joined_room_update);
188 }
189 RoomUpdateKind::Left(left_room_update) => {
190 room_updates.left.insert(room_id, left_room_update);
191 }
192 RoomUpdateKind::Invited(invited_room_update) => {
193 room_updates.invited.insert(room_id, invited_room_update);
194 }
195 RoomUpdateKind::Knocked(knocked_room_update) => {
196 room_updates.knocked.insert(room_id, knocked_room_update);
197 }
198 }
199 }
200
201 processors::room::msc4186::extensions::dispatch_typing_ephemeral_events(
205 &extensions.typing,
206 &mut room_updates.joined,
207 );
208
209 processors::room::msc4186::extensions::room_account_data(
211 &mut context,
212 &extensions.account_data,
213 &mut room_updates,
214 &self.state_store,
215 );
216
217 global_account_data_processor.apply(&mut context, &state_store).await;
218
219 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
220
221 processors::changes::save_and_apply(
223 context,
224 &self.state_store,
225 &self.ignore_user_list_changes,
226 None,
227 )
228 .await?;
229
230 let mut context = processors::Context::default();
231
232 processors::room::display_name::update_for_rooms(
235 &mut context,
236 &room_updates,
237 &self.state_store,
238 )
239 .await;
240
241 processors::changes::save_only(context, &self.state_store).await?;
243
244 Ok(SyncResponse {
245 rooms: room_updates,
246 notifications,
247 presence: Default::default(),
248 account_data: extensions.account_data.global.clone(),
249 to_device: Default::default(),
250 })
251 }
252
253 #[doc(hidden)]
256 pub async fn process_sliding_sync_receipts_extension_for_room(
257 &self,
258 room_id: &OwnedRoomId,
259 response: &http::Response,
260 new_sync_events: Vec<TimelineEvent>,
261 room_previous_events: Vec<TimelineEvent>,
262 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
263 let mut context = processors::Context::default();
264
265 let mut save_context = false;
266
267 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
269 response.extensions.receipts.rooms.get(room_id)
270 {
271 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
272 &mut context,
273 room_id,
274 receipt_ephemeral_event,
275 );
276 save_context = true;
277 Some(receipt_ephemeral_event.clone())
278 } else {
279 None
280 };
281
282 let user_id = &self.session_meta().expect("logged in user").user_id;
283
284 if let Some(mut room_info) = self.get_room(room_id).map(|room| room.clone_info()) {
287 let prev_read_receipts = room_info.read_receipts.clone();
288
289 compute_unread_counts(
290 user_id,
291 room_id,
292 context.state_changes.receipts.get(room_id),
293 room_previous_events,
294 &new_sync_events,
295 &mut room_info.read_receipts,
296 self.threading_support,
297 );
298
299 if prev_read_receipts != room_info.read_receipts {
300 context
301 .room_info_notable_updates
302 .entry(room_id.clone())
303 .or_default()
304 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
305
306 context.state_changes.add_room(room_info);
307 save_context = true;
308 }
309 }
310
311 if save_context {
313 processors::changes::save_only(context, &self.state_store).await?;
314 }
315
316 Ok(receipt_ephemeral_event)
317 }
318}
319
320#[cfg(all(test, not(target_family = "wasm")))]
321mod tests {
322 use std::collections::{BTreeMap, HashSet};
323 #[cfg(feature = "e2e-encryption")]
324 use std::sync::{Arc, RwLock as SyncRwLock};
325
326 use assert_matches::assert_matches;
327 use matrix_sdk_common::deserialized_responses::TimelineEvent;
328 #[cfg(feature = "e2e-encryption")]
329 use matrix_sdk_common::{
330 deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
331 ring_buffer::RingBuffer,
332 };
333 use matrix_sdk_test::async_test;
334 use ruma::{
335 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
336 api::client::sync::sync_events::UnreadNotificationsCount,
337 assign, event_id,
338 events::{
339 AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
340 StateEventContent, StateEventType,
341 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
342 room::{
343 avatar::RoomAvatarEventContent,
344 canonical_alias::RoomCanonicalAliasEventContent,
345 encryption::RoomEncryptionEventContent,
346 member::{MembershipState, RoomMemberEventContent},
347 message::SyncRoomMessageEvent,
348 name::RoomNameEventContent,
349 pinned_events::RoomPinnedEventsEventContent,
350 },
351 },
352 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
353 serde::Raw,
354 uint, user_id,
355 };
356 use serde_json::json;
357
358 use super::http;
359 #[cfg(feature = "e2e-encryption")]
360 use super::processors::room::msc4186::cache_latest_events;
361 use crate::{
362 BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
363 SessionMeta,
364 client::ThreadingSupport,
365 room::{RoomHero, RoomInfoNotableUpdateReasons},
366 store::{RoomLoadSettings, StoreConfig},
367 test_utils::logged_in_base_client,
368 };
369 #[cfg(feature = "e2e-encryption")]
370 use crate::{Room, store::MemoryStore};
371
372 #[async_test]
373 async fn test_notification_count_set() {
374 let client = logged_in_base_client(None).await;
375
376 let mut response = http::Response::new("42".to_owned());
377 let room_id = room_id!("!room:example.org");
378 let count = assign!(UnreadNotificationsCount::default(), {
379 highlight_count: Some(uint!(13)),
380 notification_count: Some(uint!(37)),
381 });
382
383 response.rooms.insert(
384 room_id.to_owned(),
385 assign!(http::response::Room::new(), {
386 unread_notifications: count.clone()
387 }),
388 );
389
390 let sync_response = client
391 .process_sliding_sync(&response, &RequestedRequiredStates::default())
392 .await
393 .expect("Failed to process sync");
394
395 let room = sync_response.rooms.joined.get(room_id).unwrap();
397 assert_eq!(room.unread_notifications, count.clone().into());
398
399 let room = client.get_room(room_id).expect("found room");
401 assert_eq!(room.unread_notification_counts(), count.into());
402 }
403
404 #[async_test]
405 async fn test_can_process_empty_sliding_sync_response() {
406 let client = logged_in_base_client(None).await;
407 let empty_response = http::Response::new("5".to_owned());
408 client
409 .process_sliding_sync(&empty_response, &RequestedRequiredStates::default())
410 .await
411 .expect("Failed to process sync");
412 }
413
414 #[async_test]
415 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
416 let client = logged_in_base_client(None).await;
418 let room_id = room_id!("!r:e.uk");
419
420 let mut room = http::response::Room::new();
423 room.joined_count = Some(uint!(41));
424 let response = response_with_room(room_id, room);
425 let sync_resp = client
426 .process_sliding_sync(&response, &RequestedRequiredStates::default())
427 .await
428 .expect("Failed to process sync");
429
430 let client_room = client.get_room(room_id).expect("No room found");
432 assert_eq!(client_room.room_id(), room_id);
433 assert_eq!(client_room.joined_members_count(), 41);
434 assert_eq!(client_room.state(), RoomState::Joined);
435
436 assert!(sync_resp.rooms.joined.contains_key(room_id));
438 assert!(!sync_resp.rooms.left.contains_key(room_id));
439 assert!(!sync_resp.rooms.invited.contains_key(room_id));
440 }
441
442 #[async_test]
443 async fn test_missing_room_name_event() {
444 let client = logged_in_base_client(None).await;
446 let room_id = room_id!("!r:e.uk");
447
448 let mut room = http::response::Room::new();
451 room.name = Some("little room".to_owned());
452 let response = response_with_room(room_id, room);
453 let sync_resp = client
454 .process_sliding_sync(&response, &RequestedRequiredStates::default())
455 .await
456 .expect("Failed to process sync");
457
458 let client_room = client.get_room(room_id).expect("No room found");
460 assert!(client_room.name().is_none());
461 assert_eq!(
462 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
463 "Empty Room"
464 );
465 assert_eq!(client_room.state(), RoomState::Joined);
466
467 assert!(sync_resp.rooms.joined.contains_key(room_id));
469 assert!(!sync_resp.rooms.left.contains_key(room_id));
470 assert!(!sync_resp.rooms.invited.contains_key(room_id));
471 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
472 }
473
474 #[async_test]
475 async fn test_room_name_event() {
476 let client = logged_in_base_client(None).await;
478 let room_id = room_id!("!r:e.uk");
479
480 let mut room = http::response::Room::new();
483
484 room.name = Some("little room".to_owned());
485 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
486
487 let response = response_with_room(room_id, room);
488 client
489 .process_sliding_sync(&response, &RequestedRequiredStates::default())
490 .await
491 .expect("Failed to process sync");
492
493 let client_room = client.get_room(room_id).expect("No room found");
495 assert_eq!(client_room.name().as_deref(), Some("The Name"));
496 assert_eq!(
497 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
498 "The Name"
499 );
500 }
501
502 #[async_test]
503 async fn test_missing_invited_room_name_event() {
504 let client = logged_in_base_client(None).await;
506 let room_id = room_id!("!r:e.uk");
507 let user_id = user_id!("@w:e.uk");
508 let inviter = user_id!("@john:mastodon.org");
509
510 let mut room = http::response::Room::new();
513 set_room_invited(&mut room, inviter, user_id);
514 room.name = Some("name from sliding sync response".to_owned());
515 let response = response_with_room(room_id, room);
516 let sync_resp = client
517 .process_sliding_sync(&response, &RequestedRequiredStates::default())
518 .await
519 .expect("Failed to process sync");
520
521 let client_room = client.get_room(room_id).expect("No room found");
523 assert!(client_room.name().is_none());
524
525 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
527
528 assert_eq!(client_room.state(), RoomState::Invited);
529
530 assert!(!sync_resp.rooms.joined.contains_key(room_id));
532 assert!(!sync_resp.rooms.left.contains_key(room_id));
533 assert!(sync_resp.rooms.invited.contains_key(room_id));
534 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
535 }
536
537 #[async_test]
538 async fn test_invited_room_name_event() {
539 let client = logged_in_base_client(None).await;
541 let room_id = room_id!("!r:e.uk");
542 let user_id = user_id!("@w:e.uk");
543 let inviter = user_id!("@john:mastodon.org");
544
545 let mut room = http::response::Room::new();
548
549 set_room_invited(&mut room, inviter, user_id);
550
551 room.name = Some("name from sliding sync response".to_owned());
552 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
553
554 let response = response_with_room(room_id, room);
555 client
556 .process_sliding_sync(&response, &RequestedRequiredStates::default())
557 .await
558 .expect("Failed to process sync");
559
560 let client_room = client.get_room(room_id).expect("No room found");
562 assert_eq!(client_room.name().as_deref(), Some("The Name"));
563 assert_eq!(
564 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
565 "The Name"
566 );
567 }
568
569 #[async_test]
570 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
571 let client = logged_in_base_client(None).await;
573 let room_id = room_id!("!r:e.uk");
574 let user_id = client.session_meta().unwrap().user_id.to_owned();
575
576 let mut room = http::response::Room::new();
579 set_room_knocked(&mut room, &user_id);
580
581 let response = response_with_room(room_id, room);
582 client
583 .process_sliding_sync(&response, &RequestedRequiredStates::default())
584 .await
585 .expect("Failed to process sync");
586
587 let client_room = client.get_room(room_id).expect("No room found");
589 assert_eq!(client_room.state(), RoomState::Knocked);
590 }
591
592 #[async_test]
593 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
594 {
595 let client = logged_in_base_client(None).await;
597 let room_id = room_id!("!r:e.uk");
598 let user_id = user_id!("@w:e.uk");
599
600 let mut room = http::response::Room::new();
602 set_room_knocked(&mut room, user_id);
603
604 let response = response_with_room(room_id, room);
605 client
606 .process_sliding_sync(&response, &RequestedRequiredStates::default())
607 .await
608 .expect("Failed to process sync");
609
610 let client_room = client.get_room(room_id).expect("No room found");
613 assert_eq!(client_room.state(), RoomState::Invited);
614 }
615
616 #[async_test]
617 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
618 {
619 let client = logged_in_base_client(None).await;
621 let room_id = room_id!("!r:e.uk");
622 let user_id = client.session_meta().unwrap().user_id.to_owned();
623
624 let mut room = http::response::Room::new();
626 let event = Raw::new(&json!({
627 "type": "m.room.member",
628 "sender": user_id,
629 "content": {
630 "is_direct": true,
631 "membership": "join",
632 },
633 "state_key": user_id,
634 }))
635 .expect("Failed to make raw event")
636 .cast_unchecked();
637 room.invite_state = Some(vec![event]);
638
639 let response = response_with_room(room_id, room);
640 client
641 .process_sliding_sync(&response, &RequestedRequiredStates::default())
642 .await
643 .expect("Failed to process sync");
644
645 let client_room = client.get_room(room_id).expect("No room found");
647 assert_eq!(client_room.state(), RoomState::Invited);
648 }
649
650 #[async_test]
651 async fn test_left_a_room_from_required_state_event() {
652 let client = logged_in_base_client(None).await;
654 let room_id = room_id!("!r:e.uk");
655 let user_id = user_id!("@u:e.uk");
656
657 let mut room = http::response::Room::new();
659 set_room_joined(&mut room, user_id);
660 let response = response_with_room(room_id, room);
661 client
662 .process_sliding_sync(&response, &RequestedRequiredStates::default())
663 .await
664 .expect("Failed to process sync");
665 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
666
667 let mut room = http::response::Room::new();
669 set_room_left(&mut room, user_id);
670 let response = response_with_room(room_id, room);
671 let sync_resp = client
672 .process_sliding_sync(&response, &RequestedRequiredStates::default())
673 .await
674 .expect("Failed to process sync");
675
676 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
678
679 assert!(!sync_resp.rooms.joined.contains_key(room_id));
681 assert!(sync_resp.rooms.left.contains_key(room_id));
682 assert!(!sync_resp.rooms.invited.contains_key(room_id));
683 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
684 }
685
686 #[async_test]
687 async fn test_kick_or_ban_updates_room_to_left() {
688 for membership in [MembershipState::Leave, MembershipState::Ban] {
689 let room_id = room_id!("!r:e.uk");
690 let user_a_id = user_id!("@a:e.uk");
691 let user_b_id = user_id!("@b:e.uk");
692 let client = logged_in_base_client(Some(user_a_id)).await;
693
694 let mut room = http::response::Room::new();
696 set_room_joined(&mut room, user_a_id);
697 let response = response_with_room(room_id, room);
698 client
699 .process_sliding_sync(&response, &RequestedRequiredStates::default())
700 .await
701 .expect("Failed to process sync");
702 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
703
704 let mut room = http::response::Room::new();
706 room.required_state.push(make_state_event(
707 user_b_id,
708 user_a_id.as_str(),
709 RoomMemberEventContent::new(membership.clone()),
710 None,
711 ));
712 let response = response_with_room(room_id, room);
713 let sync_resp = client
714 .process_sliding_sync(&response, &RequestedRequiredStates::default())
715 .await
716 .expect("Failed to process sync");
717
718 match membership {
719 MembershipState::Leave => {
720 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
722 }
723 MembershipState::Ban => {
724 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
726 }
727 _ => panic!("Unexpected membership state found: {membership}"),
728 }
729
730 assert!(!sync_resp.rooms.joined.contains_key(room_id));
732 assert!(sync_resp.rooms.left.contains_key(room_id));
733 assert!(!sync_resp.rooms.invited.contains_key(room_id));
734 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
735 }
736 }
737
738 #[async_test]
739 async fn test_left_a_room_from_timeline_state_event() {
740 let client = logged_in_base_client(None).await;
742 let room_id = room_id!("!r:e.uk");
743 let user_id = user_id!("@u:e.uk");
744
745 let mut room = http::response::Room::new();
747 set_room_joined(&mut room, user_id);
748 let response = response_with_room(room_id, room);
749 client
750 .process_sliding_sync(&response, &RequestedRequiredStates::default())
751 .await
752 .expect("Failed to process sync");
753 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
754
755 let mut room = http::response::Room::new();
757 set_room_left_as_timeline_event(&mut room, user_id);
758 let response = response_with_room(room_id, room);
759 client
760 .process_sliding_sync(&response, &RequestedRequiredStates::default())
761 .await
762 .expect("Failed to process sync");
763
764 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
766 }
767
768 #[async_test]
769 async fn test_can_be_reinvited_to_a_left_room() {
770 let client = logged_in_base_client(None).await;
774 let room_id = room_id!("!r:e.uk");
775 let user_id = user_id!("@u:e.uk");
776
777 let mut room = http::response::Room::new();
779 set_room_joined(&mut room, user_id);
780 let response = response_with_room(room_id, room);
781 client
782 .process_sliding_sync(&response, &RequestedRequiredStates::default())
783 .await
784 .expect("Failed to process sync");
785 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
787
788 let mut room = http::response::Room::new();
790 set_room_left(&mut room, user_id);
791 let response = response_with_room(room_id, room);
792 client
793 .process_sliding_sync(&response, &RequestedRequiredStates::default())
794 .await
795 .expect("Failed to process sync");
796 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
798
799 let mut room = http::response::Room::new();
801 set_room_invited(&mut room, user_id, user_id);
802 let response = response_with_room(room_id, room);
803 client
804 .process_sliding_sync(&response, &RequestedRequiredStates::default())
805 .await
806 .expect("Failed to process sync");
807
808 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
810 }
811
812 #[async_test]
813 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
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::Join).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::Join);
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_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
842 {
843 let room_id = room_id!("!r:e.uk");
844 let user_a_id = user_id!("@a:e.uk");
845 let user_b_id = user_id!("@b:e.uk");
846
847 let client = logged_in_base_client(None).await;
849 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
850
851 assert!(
853 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
854 );
855 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
856
857 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
859
860 assert!(
864 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
865 );
866 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
867 }
868
869 #[async_test]
870 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
871 let room_id = room_id!("!r:bar.org");
872 let user_a_id = user_id!("@a:bar.org");
873 let user_b_id = user_id!("@b:bar.org");
874
875 let client = logged_in_base_client(None).await;
877 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
878
879 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
881
882 assert!(
884 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
885 );
886 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
887
888 let room = client.get_room(room_id).unwrap();
889
890 assert_eq!(room.active_members_count(), 2);
891 assert_eq!(room.joined_members_count(), 2);
892 assert_eq!(room.invited_members_count(), 0);
893 }
894
895 #[async_test]
896 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
897 let room_id = room_id!("!r:bar.org");
898 let user_a_id = user_id!("@a:bar.org");
899 let user_b_id = user_id!("@b:bar.org");
900
901 let client = logged_in_base_client(None).await;
903 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
904
905 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
907
908 assert!(
910 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
911 );
912 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
913
914 let room = client.get_room(room_id).unwrap();
915
916 assert_eq!(room.active_members_count(), 2);
917 assert_eq!(room.joined_members_count(), 1);
918 assert_eq!(room.invited_members_count(), 1);
919 }
920
921 #[async_test]
922 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
923 let client = logged_in_base_client(None).await;
925 let room_id = room_id!("!r:e.uk");
926
927 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
948 #[async_test]
949 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
950 let client = logged_in_base_client(None).await;
952 let room_id = room_id!("!r:e.uk");
953
954 let room = {
958 let mut room = http::response::Room::new();
959 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
960
961 room
962 };
963 let response = response_with_room(room_id, room);
964 client
965 .process_sliding_sync(&response, &RequestedRequiredStates::default())
966 .await
967 .expect("Failed to process sync");
968
969 let client_room = client.get_room(room_id).expect("No room found");
971 assert_eq!(
972 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
973 "med1"
974 );
975
976 let room = http::response::Room::new();
980 let response = response_with_room(room_id, room);
981 client
982 .process_sliding_sync(&response, &RequestedRequiredStates::default())
983 .await
984 .expect("Failed to process sync");
985
986 let client_room = client.get_room(room_id).expect("No room found");
988 assert_eq!(
989 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
990 "med1"
991 );
992
993 let room = {
997 let mut room = http::response::Room::new();
998 room.avatar = JsOption::Null;
999
1000 room
1001 };
1002 let response = response_with_room(room_id, room);
1003 client
1004 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1005 .await
1006 .expect("Failed to process sync");
1007
1008 let client_room = client.get_room(room_id).expect("No room found");
1010 assert!(client_room.avatar_url().is_none());
1011 }
1012
1013 #[async_test]
1014 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1015 let client = logged_in_base_client(None).await;
1017 let room_id = room_id!("!r:e.uk");
1018 let user_id = user_id!("@u:e.uk");
1019
1020 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1022 let response = response_with_room(room_id, room);
1023 client
1024 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1025 .await
1026 .expect("Failed to process sync");
1027
1028 let client_room = client.get_room(room_id).expect("No room found");
1030 assert_eq!(
1031 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1032 "med1"
1033 );
1034 }
1035
1036 #[async_test]
1037 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1038 let client = logged_in_base_client(None).await;
1040 let room_id = room_id!("!r:e.uk");
1041 let user_id = user_id!("@u:e.uk");
1042
1043 let mut room = http::response::Room::new();
1045 set_room_invited(&mut room, user_id, user_id);
1046 let response = response_with_room(room_id, room);
1047 let sync_resp = client
1048 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1049 .await
1050 .expect("Failed to process sync");
1051
1052 let client_room = client.get_room(room_id).expect("No room found");
1054 assert_eq!(client_room.room_id(), room_id);
1055 assert_eq!(client_room.state(), RoomState::Invited);
1056
1057 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1059 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1060 }
1061
1062 #[async_test]
1063 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1064 let client = logged_in_base_client(None).await;
1066 let room_id = room_id!("!r:e.uk");
1067 let user_id = user_id!("@u:e.uk");
1068
1069 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1071 set_room_invited(&mut room, user_id, user_id);
1072 let response = response_with_room(room_id, room);
1073 client
1074 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1075 .await
1076 .expect("Failed to process sync");
1077
1078 let client_room = client.get_room(room_id).expect("No room found");
1080 assert_eq!(
1081 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1082 "med1"
1083 );
1084 }
1085
1086 #[async_test]
1087 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1088 {
1089 let client = logged_in_base_client(None).await;
1091 let room_id = room_id!("!r:e.uk");
1092 let user_id = user_id!("@u:e.uk");
1093 let room_alias_id = room_alias_id!("#myroom:e.uk");
1094
1095 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1097 set_room_invited(&mut room, user_id, user_id);
1098 let response = response_with_room(room_id, room);
1099 client
1100 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1101 .await
1102 .expect("Failed to process sync");
1103
1104 let client_room = client.get_room(room_id).expect("No room found");
1106 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1107 }
1108
1109 #[async_test]
1110 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1111 let client = logged_in_base_client(None).await;
1113 let room_id = room_id!("!r:e.uk");
1114 let user_id = user_id!("@u:e.uk");
1115 let room_alias_id = room_alias_id!("#myroom:e.uk");
1116
1117 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1120 room.name = Some("This came from the server".to_owned());
1121 let response = response_with_room(room_id, room);
1122 client
1123 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1124 .await
1125 .expect("Failed to process sync");
1126
1127 let client_room = client.get_room(room_id).expect("No room found");
1129 assert_eq!(
1130 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1131 "myroom"
1132 );
1133 assert!(client_room.name().is_none());
1134 }
1135
1136 #[async_test]
1137 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1138 let client = logged_in_base_client(None).await;
1139 let user_id = user_id!("@u:e.uk");
1140 let room_id = room_id!("!r:e.uk");
1141
1142 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1143
1144 let room = room_with_name("Hello World", user_id);
1145 let response = response_with_room(room_id, room);
1146 client
1147 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1148 .await
1149 .expect("Failed to process sync");
1150
1151 let room = client.get_room(room_id).expect("No room found");
1152 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1153
1154 assert_matches!(
1155 room_info_notable_update.recv().await,
1156 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1157 assert_eq!(received_room_id, room_id);
1158 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1159 }
1160 );
1161 assert_matches!(
1162 room_info_notable_update.recv().await,
1163 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1164 assert_eq!(received_room_id, room_id);
1165 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1167 }
1168 );
1169 assert!(room_info_notable_update.is_empty());
1170 }
1171
1172 #[async_test]
1173 async fn test_display_name_is_persisted_from_sliding_sync() {
1174 let user_id = user_id!("@u:e.uk");
1175 let room_id = room_id!("!r:e.uk");
1176 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1177 let state_store;
1178
1179 {
1180 let client = {
1181 let store = StoreConfig::new("cross-process-foo".to_owned());
1182 state_store = store.state_store.clone();
1183
1184 let client = BaseClient::new(store, ThreadingSupport::Disabled);
1185 client
1186 .activate(
1187 session_meta.clone(),
1188 RoomLoadSettings::default(),
1189 #[cfg(feature = "e2e-encryption")]
1190 None,
1191 )
1192 .await
1193 .expect("`activate` failed!");
1194
1195 client
1196 };
1197
1198 let room = room_with_name("Hello World", user_id);
1201 let response = response_with_room(room_id, room);
1202 client
1203 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1204 .await
1205 .expect("Failed to process sync");
1206
1207 let room = client.get_room(room_id).expect("No room found");
1208 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1209 }
1210
1211 {
1212 let client = {
1213 let mut store = StoreConfig::new("cross-process-foo".to_owned());
1214 store.state_store = state_store;
1215 let client = BaseClient::new(store, ThreadingSupport::Disabled);
1216 client
1217 .activate(
1218 session_meta,
1219 RoomLoadSettings::default(),
1220 #[cfg(feature = "e2e-encryption")]
1221 None,
1222 )
1223 .await
1224 .expect("`activate` failed!");
1225
1226 client
1227 };
1228
1229 let room = client.get_room(room_id).expect("No room found");
1230 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1231 }
1232 }
1233
1234 #[async_test]
1235 async fn test_compute_heroes_from_sliding_sync() {
1236 let client = logged_in_base_client(None).await;
1238 let room_id = room_id!("!r:e.uk");
1239 let gordon = user_id!("@gordon:e.uk").to_owned();
1240 let alice = user_id!("@alice:e.uk").to_owned();
1241
1242 let mut room = http::response::Room::new();
1245 room.heroes = Some(vec![
1246 assign!(http::response::Hero::new(gordon), {
1247 name: Some("Gordon".to_owned()),
1248 }),
1249 assign!(http::response::Hero::new(alice), {
1250 name: Some("Alice".to_owned()),
1251 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1252 }),
1253 ]);
1254 let response = response_with_room(room_id, room);
1255 let _sync_resp = client
1256 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1257 .await
1258 .expect("Failed to process sync");
1259
1260 let client_room = client.get_room(room_id).expect("No room found");
1262 assert_eq!(client_room.room_id(), room_id);
1263 assert_eq!(client_room.state(), RoomState::Joined);
1264
1265 assert_eq!(
1267 client_room.clone_info().summary.heroes(),
1268 &[
1269 RoomHero {
1270 user_id: owned_user_id!("@gordon:e.uk"),
1271 display_name: Some("Gordon".to_owned()),
1272 avatar_url: None
1273 },
1274 RoomHero {
1275 user_id: owned_user_id!("@alice:e.uk"),
1276 display_name: Some("Alice".to_owned()),
1277 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1278 },
1279 ]
1280 );
1281 }
1282
1283 #[async_test]
1284 async fn test_last_event_from_sliding_sync_is_cached() {
1285 let client = logged_in_base_client(None).await;
1287 let room_id = room_id!("!r:e.uk");
1288 let event_a = json!({
1289 "sender":"@alice:example.com",
1290 "type":"m.room.message",
1291 "event_id": "$ida",
1292 "origin_server_ts": 12344446,
1293 "content":{"body":"A", "msgtype": "m.text"}
1294 });
1295 let event_b = json!({
1296 "sender":"@alice:example.com",
1297 "type":"m.room.message",
1298 "event_id": "$idb",
1299 "origin_server_ts": 12344447,
1300 "content":{"body":"B", "msgtype": "m.text"}
1301 });
1302
1303 let events = &[event_a, event_b.clone()];
1305 let room = room_with_timeline(events);
1306 let response = response_with_room(room_id, room);
1307 client
1308 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1309 .await
1310 .expect("Failed to process sync");
1311
1312 let client_room = client.get_room(room_id).expect("No room found");
1314 assert_eq!(
1315 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1316 "$idb"
1317 );
1318 }
1319
1320 #[async_test]
1321 async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1322 let own_user_id = user_id!("@me:e.uk");
1323 let client = logged_in_base_client(Some(own_user_id)).await;
1325 let room_id = room_id!("!r:e.uk");
1326
1327 let create = json!({
1329 "sender":"@ignacio:example.com",
1330 "state_key":"",
1331 "type":"m.room.create",
1332 "event_id": "$idc",
1333 "origin_server_ts": 12344415,
1334 "content":{ "room_version": "11" },
1335 "room_id": room_id,
1336 });
1337
1338 let power_levels = json!({
1340 "sender":"@alice:example.com",
1341 "state_key":"",
1342 "type":"m.room.power_levels",
1343 "event_id": "$idb",
1344 "origin_server_ts": 12344445,
1345 "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1346 "room_id": room_id,
1347 });
1348
1349 let knock_event = json!({
1351 "sender":"@alice:example.com",
1352 "state_key":"@alice:example.com",
1353 "type":"m.room.member",
1354 "event_id": "$ida",
1355 "origin_server_ts": 12344446,
1356 "content":{"membership": "knock"},
1357 "room_id": room_id,
1358 });
1359
1360 let events = &[knock_event];
1362 let mut room = room_with_timeline(events);
1363 room.required_state.extend([
1364 Raw::new(&create).unwrap().cast_unchecked(),
1365 Raw::new(&power_levels).unwrap().cast_unchecked(),
1366 ]);
1367 let response = response_with_room(room_id, room);
1368 client
1369 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1370 .await
1371 .expect("Failed to process sync");
1372
1373 let client_room = client.get_room(room_id).expect("No room found");
1375 assert_eq!(
1376 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1377 "$ida"
1378 );
1379 }
1380
1381 #[async_test]
1382 async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1383 let own_user_id = user_id!("@me:e.uk");
1384 let client = logged_in_base_client(Some(own_user_id)).await;
1386 let room_id = room_id!("!r:e.uk");
1387
1388 let power_levels = json!({
1391 "sender":"@alice:example.com",
1392 "state_key":"",
1393 "type":"m.room.power_levels",
1394 "event_id": "$idb",
1395 "origin_server_ts": 12344445,
1396 "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1397 "room_id": room_id,
1398 });
1399
1400 let knock_event = json!({
1402 "sender":"@alice:example.com",
1403 "state_key":"@alice:example.com",
1404 "type":"m.room.member",
1405 "event_id": "$ida",
1406 "origin_server_ts": 12344446,
1407 "content":{"membership": "knock"},
1408 "room_id": room_id,
1409 });
1410
1411 let events = &[knock_event];
1413 let mut room = room_with_timeline(events);
1414 room.required_state.push(Raw::new(&power_levels).unwrap().cast_unchecked());
1415 let response = response_with_room(room_id, room);
1416 client
1417 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1418 .await
1419 .expect("Failed to process sync");
1420
1421 let client_room = client.get_room(room_id).expect("No room found");
1423 assert!(client_room.latest_event().is_none());
1424 }
1425
1426 #[async_test]
1427 async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1428 let client = logged_in_base_client(None).await;
1430 let room_id = room_id!("!r:e.uk");
1431 let join_event = json!({
1433 "sender":"@alice:example.com",
1434 "state_key":"@alice:example.com",
1435 "type":"m.room.member",
1436 "event_id": "$ida",
1437 "origin_server_ts": 12344446,
1438 "content":{"membership": "join"},
1439 "room_id": room_id,
1440 });
1441
1442 let events = &[join_event];
1444 let room = room_with_timeline(events);
1445 let response = response_with_room(room_id, room);
1446 client
1447 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1448 .await
1449 .expect("Failed to process sync");
1450
1451 let client_room = client.get_room(room_id).expect("No room found");
1453 assert!(client_room.latest_event().is_none());
1454 }
1455
1456 #[cfg(feature = "e2e-encryption")]
1457 #[async_test]
1458 async fn test_cached_latest_event_can_be_redacted() {
1459 let client = logged_in_base_client(None).await;
1461 let room_id = room_id!("!r:e.uk");
1462 let event_a = json!({
1463 "sender": "@alice:example.com",
1464 "type": "m.room.message",
1465 "event_id": "$ida",
1466 "origin_server_ts": 12344446,
1467 "content": { "body":"A", "msgtype": "m.text" },
1468 });
1469
1470 let room = room_with_timeline(&[event_a]);
1472 let response = response_with_room(room_id, room);
1473 client
1474 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1475 .await
1476 .expect("Failed to process sync");
1477
1478 let client_room = client.get_room(room_id).expect("No room found");
1480 assert_eq!(
1481 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1482 "$ida"
1483 );
1484
1485 let redaction = json!({
1486 "sender": "@alice:example.com",
1487 "type": "m.room.redaction",
1488 "event_id": "$idb",
1489 "redacts": "$ida",
1490 "origin_server_ts": 12344448,
1491 "content": {},
1492 });
1493
1494 let room = room_with_timeline(&[redaction]);
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 let client_room = client.get_room(room_id).expect("No room found");
1504 let latest_event = client_room.latest_event().unwrap();
1505 assert_eq!(latest_event.event_id().unwrap(), "$ida");
1506
1507 assert_matches!(
1509 latest_event.event().raw().deserialize().unwrap(),
1510 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1511 SyncRoomMessageEvent::Redacted(_)
1512 ))
1513 );
1514 }
1515
1516 #[cfg(feature = "e2e-encryption")]
1517 #[async_test]
1518 async fn test_when_no_events_we_dont_cache_any() {
1519 let events = &[];
1520 let chosen = choose_event_to_cache(events).await;
1521 assert!(chosen.is_none());
1522 }
1523
1524 #[cfg(feature = "e2e-encryption")]
1525 #[async_test]
1526 async fn test_when_only_one_event_we_cache_it() {
1527 let event1 = make_event("m.room.message", "$1");
1528 let events = std::slice::from_ref(&event1);
1529 let chosen = choose_event_to_cache(events).await;
1530 assert_eq!(ev_id(chosen), rawev_id(event1));
1531 }
1532
1533 #[cfg(feature = "e2e-encryption")]
1534 #[async_test]
1535 async fn test_with_multiple_events_we_cache_the_last_one() {
1536 let event1 = make_event("m.room.message", "$1");
1537 let event2 = make_event("m.room.message", "$2");
1538 let events = &[event1, event2.clone()];
1539 let chosen = choose_event_to_cache(events).await;
1540 assert_eq!(ev_id(chosen), rawev_id(event2));
1541 }
1542
1543 #[cfg(feature = "e2e-encryption")]
1544 #[async_test]
1545 async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1546 let event1 = make_event("m.room.message", "$1");
1547 let event2 = make_event("m.room.message", "$2");
1548 let event3 = make_event("m.room.powerlevels", "$3");
1549 let event4 = make_event("m.room.powerlevels", "$5");
1550 let events = &[event1, event2.clone(), event3, event4];
1551 let chosen = choose_event_to_cache(events).await;
1552 assert_eq!(ev_id(chosen), rawev_id(event2));
1553 }
1554
1555 #[cfg(feature = "e2e-encryption")]
1556 #[async_test]
1557 async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1558 let event1 = make_event("m.room.power_levels", "$1");
1559 let events = &[event1];
1560 let chosen = choose_event_to_cache(events).await;
1561 assert!(chosen.is_none());
1562 }
1563
1564 #[cfg(feature = "e2e-encryption")]
1565 #[async_test]
1566 async fn test_cache_encrypted_events_that_are_after_latest_message() {
1567 let event1 = make_event("m.room.message", "$1");
1569 let event2 = make_event("m.room.message", "$2");
1570 let event3 = make_encrypted_event("$3");
1571 let event4 = make_encrypted_event("$4");
1572 let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
1573
1574 let room = make_room();
1576 let mut room_info = room.clone_info();
1577 cache_latest_events(&room, &mut room_info, events, None, None).await;
1578
1579 assert_eq!(
1581 ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
1582 rawev_id(event2.clone())
1583 );
1584
1585 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1586 assert_eq!(
1587 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1588 rawev_id(event2)
1589 );
1590
1591 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
1593 }
1594
1595 #[cfg(feature = "e2e-encryption")]
1596 #[async_test]
1597 async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
1598 let event1 = make_encrypted_event("$1");
1600 let event2 = make_event("m.room.message", "$2");
1601 let event3 = make_encrypted_event("$3");
1602 let events = &[event1, event2.clone(), event3.clone()];
1603
1604 let room = make_room();
1606 let mut room_info = room.clone_info();
1607 cache_latest_events(&room, &mut room_info, events, None, None).await;
1608 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1609
1610 assert_eq!(
1612 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1613 rawev_id(event2)
1614 );
1615
1616 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
1618 }
1619
1620 #[cfg(feature = "e2e-encryption")]
1621 #[async_test]
1622 async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
1623 let event1 = make_event("m.room.message", "$1");
1626 let event2 = make_event("m.room.message", "$2");
1627 let event3 = make_encrypted_event("$3");
1628 let event4 = make_event("m.read", "$4");
1629 let event5 = make_encrypted_event("$5");
1630 let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
1631
1632 let room = make_room();
1634 let mut room_info = room.clone_info();
1635 cache_latest_events(&room, &mut room_info, events, None, None).await;
1636 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1637
1638 assert_eq!(
1640 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1641 rawev_id(event2)
1642 );
1643
1644 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
1646 }
1647
1648 #[cfg(feature = "e2e-encryption")]
1649 #[async_test]
1650 async fn test_only_store_the_max_number_of_encrypted_events() {
1651 let evente = make_event("m.room.message", "$e");
1654 let eventd = make_event("m.room.message", "$d");
1655 let eventc = make_encrypted_event("$c");
1656 let event9 = make_encrypted_event("$9");
1657 let event8 = make_encrypted_event("$8");
1658 let event7 = make_encrypted_event("$7");
1659 let eventb = make_event("m.read", "$b");
1660 let event6 = make_encrypted_event("$6");
1661 let event5 = make_encrypted_event("$5");
1662 let event4 = make_encrypted_event("$4");
1663 let event3 = make_encrypted_event("$3");
1664 let event2 = make_encrypted_event("$2");
1665 let eventa = make_event("m.read", "$a");
1666 let event1 = make_encrypted_event("$1");
1667 let event0 = make_encrypted_event("$0");
1668 let events = &[
1669 evente,
1670 eventd.clone(),
1671 eventc,
1672 event9.clone(),
1673 event8.clone(),
1674 event7.clone(),
1675 eventb,
1676 event6.clone(),
1677 event5.clone(),
1678 event4.clone(),
1679 event3.clone(),
1680 event2.clone(),
1681 eventa,
1682 event1.clone(),
1683 event0.clone(),
1684 ];
1685
1686 let room = make_room();
1688 let mut room_info = room.clone_info();
1689 cache_latest_events(&room, &mut room_info, events, None, None).await;
1690 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1691
1692 assert_eq!(
1694 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1695 rawev_id(eventd)
1696 );
1697
1698 assert_eq!(
1700 rawevs_ids(&room.latest_encrypted_events),
1701 evs_ids(&[
1702 event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
1703 ])
1704 );
1705 }
1706
1707 #[cfg(feature = "e2e-encryption")]
1708 #[async_test]
1709 async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
1710 let room = make_room();
1712 let mut room_info = room.clone_info();
1713 cache_latest_events(
1714 &room,
1715 &mut room_info,
1716 &[
1717 make_encrypted_event("$0"),
1718 make_encrypted_event("$1"),
1719 make_encrypted_event("$2"),
1720 make_encrypted_event("$3"),
1721 make_encrypted_event("$4"),
1722 make_encrypted_event("$5"),
1723 make_encrypted_event("$6"),
1724 make_encrypted_event("$7"),
1725 make_encrypted_event("$8"),
1726 make_encrypted_event("$9"),
1727 ],
1728 None,
1729 None,
1730 )
1731 .await;
1732 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1733
1734 assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
1736
1737 let eventa = make_encrypted_event("$a");
1739 let mut room_info = room.clone_info();
1740 cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
1741 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1742
1743 assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
1745
1746 assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
1748 }
1749
1750 #[cfg(feature = "e2e-encryption")]
1751 #[async_test]
1752 async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
1753 let room = make_room();
1755 let mut room_info = room.clone_info();
1756 cache_latest_events(
1757 &room,
1758 &mut room_info,
1759 &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
1760 None,
1761 None,
1762 )
1763 .await;
1764 room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
1765
1766 let eventa = make_event("m.room.message", "$a");
1768 let eventb = make_encrypted_event("$b");
1769 cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
1770 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1771
1772 assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
1774
1775 assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
1777 }
1778
1779 #[async_test]
1780 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1781 let client = logged_in_base_client(None).await;
1783 let room_id = room_id!("!r:e.uk");
1784
1785 let room = assign!(http::response::Room::new(), {
1787 bump_stamp: Some(42u32.into()),
1788 });
1789 let response = response_with_room(room_id, room);
1790 client
1791 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1792 .await
1793 .expect("Failed to process sync");
1794
1795 let client_room = client.get_room(room_id).expect("No room found");
1797 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1798 }
1799
1800 #[async_test]
1801 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1802 let client = logged_in_base_client(None).await;
1804 let room_id = room_id!("!r:e.uk");
1805
1806 {
1807 let room = assign!(http::response::Room::new(), {
1809 bump_stamp: Some(42u32.into()),
1810 });
1811 let response = response_with_room(room_id, room);
1812 client
1813 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1814 .await
1815 .expect("Failed to process sync");
1816
1817 let client_room = client.get_room(room_id).expect("No room found");
1819 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1820 }
1821
1822 {
1823 let room = assign!(http::response::Room::new(), {
1825 bump_stamp: None,
1826 });
1827 let response = response_with_room(room_id, room);
1828 client
1829 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1830 .await
1831 .expect("Failed to process sync");
1832
1833 let client_room = client.get_room(room_id).expect("No room found");
1835 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1836 }
1837
1838 {
1839 let room = assign!(http::response::Room::new(), {
1842 bump_stamp: Some(153u32.into()),
1843 });
1844 let response = response_with_room(room_id, room);
1845 client
1846 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1847 .await
1848 .expect("Failed to process sync");
1849
1850 let client_room = client.get_room(room_id).expect("No room found");
1852 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
1853 }
1854 }
1855
1856 #[async_test]
1857 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1858 let client = logged_in_base_client(None).await;
1860 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1861 let room_id = room_id!("!r:e.uk");
1862
1863 let room = assign!(http::response::Room::new(), {
1865 bump_stamp: Some(42u32.into()),
1866 });
1867 let response = response_with_room(room_id, room);
1868 client
1869 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1870 .await
1871 .expect("Failed to process sync");
1872
1873 assert_matches!(
1876 room_info_notable_update_stream.recv().await,
1877 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1878 assert_eq!(received_room_id, room_id);
1879 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1880 }
1881 );
1882 assert_matches!(
1883 room_info_notable_update_stream.recv().await,
1884 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1885 assert_eq!(received_room_id, room_id);
1886 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1887 }
1888 );
1889 assert!(room_info_notable_update_stream.is_empty());
1890
1891 let room = assign!(http::response::Room::new(), {
1893 bump_stamp: Some(43u32.into()),
1894 });
1895 let response = response_with_room(room_id, room);
1896 client
1897 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1898 .await
1899 .expect("Failed to process sync");
1900
1901 assert_matches!(
1903 room_info_notable_update_stream.recv().await,
1904 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1905 assert_eq!(received_room_id, room_id);
1906 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1907 }
1908 );
1909 assert!(room_info_notable_update_stream.is_empty());
1910 }
1911
1912 #[async_test]
1913 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1914 let client = logged_in_base_client(None).await;
1916 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1917
1918 let room_id = room_id!("!r:e.uk");
1920 let room = http::response::Room::new();
1921 let response = response_with_room(room_id, room);
1922 client
1923 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1924 .await
1925 .expect("Failed to process sync");
1926
1927 assert_matches!(
1929 room_info_notable_update_stream.recv().await,
1930 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1931 assert_eq!(received_room_id, room_id);
1932 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1933 }
1934 );
1935 assert_matches!(
1936 room_info_notable_update_stream.recv().await,
1937 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1938 assert_eq!(received_room_id, room_id);
1939 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1940 }
1941 );
1942
1943 let room_id = room_id!("!r:e.uk");
1945 let events = vec![
1946 Raw::from_json_string(
1947 json!({
1948 "type": "m.room.member",
1949 "event_id": "$3",
1950 "content": { "membership": "join" },
1951 "sender": "@u:h.uk",
1952 "origin_server_ts": 12344445,
1953 "state_key": "@u:e.uk",
1954 })
1955 .to_string(),
1956 )
1957 .unwrap(),
1958 ];
1959 let room = assign!(http::response::Room::new(), {
1960 required_state: events,
1961 });
1962 let response = response_with_room(room_id, room);
1963 client
1964 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1965 .await
1966 .expect("Failed to process sync");
1967
1968 assert_matches!(
1970 room_info_notable_update_stream.recv().await,
1971 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1972 assert_eq!(received_room_id, room_id);
1973 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1974 }
1975 );
1976 assert!(room_info_notable_update_stream.is_empty());
1977
1978 let events = vec![
1979 Raw::from_json_string(
1980 json!({
1981 "type": "m.room.member",
1982 "event_id": "$3",
1983 "content": { "membership": "leave" },
1984 "sender": "@u:h.uk",
1985 "origin_server_ts": 12344445,
1986 "state_key": "@u:e.uk",
1987 })
1988 .to_string(),
1989 )
1990 .unwrap(),
1991 ];
1992 let room = assign!(http::response::Room::new(), {
1993 required_state: events,
1994 });
1995 let response = response_with_room(room_id, room);
1996 client
1997 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1998 .await
1999 .expect("Failed to process sync");
2000
2001 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::MEMBERSHIP));
2007 }
2008 );
2009 assert!(room_info_notable_update_stream.is_empty());
2010 }
2011
2012 #[async_test]
2013 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2014 let client = logged_in_base_client(None).await;
2016 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2017
2018 let room_id = room_id!("!r:e.uk");
2020 let room = http::response::Room::new();
2021 let response = response_with_room(room_id, room);
2022 client
2023 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2024 .await
2025 .expect("Failed to process sync");
2026
2027 assert_matches!(
2029 room_info_notable_update_stream.recv().await,
2030 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2031 assert_eq!(received_room_id, room_id);
2032 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2033 }
2034 );
2035 assert_matches!(
2036 room_info_notable_update_stream.recv().await,
2037 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2038 assert_eq!(received_room_id, room_id);
2039 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2040 }
2041 );
2042 assert!(room_info_notable_update_stream.is_empty());
2043
2044 let room_id = room_id!("!r:e.uk");
2047 let room_account_data_events = vec![
2048 Raw::from_json_string(
2049 json!({
2050 "type": "m.marked_unread",
2051 "event_id": "$1",
2052 "content": { "unread": true },
2053 "sender": client.session_meta().unwrap().user_id,
2054 "origin_server_ts": 12344445,
2055 })
2056 .to_string(),
2057 )
2058 .unwrap(),
2059 ];
2060 let mut response = response_with_room(room_id, http::response::Room::new());
2061 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2062
2063 client
2064 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2065 .await
2066 .expect("Failed to process sync");
2067
2068 assert_matches!(
2070 room_info_notable_update_stream.recv().await,
2071 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2072 assert_eq!(received_room_id, room_id);
2073 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2074 }
2075 );
2076
2077 client
2079 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2080 .await
2081 .expect("Failed to process sync");
2082
2083 assert_matches!(
2084 room_info_notable_update_stream.recv().await,
2085 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2086 assert_eq!(received_room_id, room_id);
2087 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2088 }
2089 );
2090 assert!(room_info_notable_update_stream.is_empty());
2091
2092 let room_account_data_events = vec![
2094 Raw::from_json_string(
2095 json!({
2096 "type": "m.marked_unread",
2097 "event_id": "$1",
2098 "content": { "unread": false },
2099 "sender": client.session_meta().unwrap().user_id,
2100 "origin_server_ts": 12344445,
2101 })
2102 .to_string(),
2103 )
2104 .unwrap(),
2105 ];
2106 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2107 client
2108 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2109 .await
2110 .expect("Failed to process sync");
2111
2112 assert_matches!(
2113 room_info_notable_update_stream.recv().await,
2114 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2115 assert_eq!(received_room_id, room_id);
2116 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2117 }
2118 );
2119 assert!(room_info_notable_update_stream.is_empty());
2120 }
2121
2122 #[async_test]
2123 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2124 let client = logged_in_base_client(None).await;
2126 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2127
2128 let room_id = room_id!("!r:e.uk");
2130 let room = http::response::Room::new();
2131 let response = response_with_room(room_id, room);
2132 client
2133 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2134 .await
2135 .expect("Failed to process sync");
2136
2137 assert_matches!(
2139 room_info_notable_update_stream.recv().await,
2140 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2141 assert_eq!(received_room_id, room_id);
2142 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2143 }
2144 );
2145 assert_matches!(
2146 room_info_notable_update_stream.recv().await,
2147 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2148 assert_eq!(received_room_id, room_id);
2149 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2150 }
2151 );
2152 assert!(room_info_notable_update_stream.is_empty());
2153
2154 let room_id = room_id!("!r:e.uk");
2157 let unstable_room_account_data_events = vec![
2158 Raw::from_json_string(
2159 json!({
2160 "type": "com.famedly.marked_unread",
2161 "event_id": "$1",
2162 "content": { "unread": true },
2163 "sender": client.session_meta().unwrap().user_id,
2164 "origin_server_ts": 12344445,
2165 })
2166 .to_string(),
2167 )
2168 .unwrap(),
2169 ];
2170 let mut response = response_with_room(room_id, http::response::Room::new());
2171 response
2172 .extensions
2173 .account_data
2174 .rooms
2175 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2176
2177 client
2178 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2179 .await
2180 .expect("Failed to process sync");
2181
2182 assert_matches!(
2184 room_info_notable_update_stream.recv().await,
2185 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2186 assert_eq!(received_room_id, room_id);
2187 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2188 }
2189 );
2190 assert!(room_info_notable_update_stream.is_empty());
2191
2192 let stable_room_account_data_events = vec![
2194 Raw::from_json_string(
2195 json!({
2196 "type": "m.marked_unread",
2197 "event_id": "$1",
2198 "content": { "unread": false },
2199 "sender": client.session_meta().unwrap().user_id,
2200 "origin_server_ts": 12344445,
2201 })
2202 .to_string(),
2203 )
2204 .unwrap(),
2205 ];
2206 response
2207 .extensions
2208 .account_data
2209 .rooms
2210 .insert(room_id.to_owned(), stable_room_account_data_events);
2211 client
2212 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2213 .await
2214 .expect("Failed to process sync");
2215
2216 assert_matches!(
2218 room_info_notable_update_stream.recv().await,
2219 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2220 assert_eq!(received_room_id, room_id);
2221 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2222 }
2223 );
2224 assert!(room_info_notable_update_stream.is_empty());
2225
2226 response
2229 .extensions
2230 .account_data
2231 .rooms
2232 .insert(room_id.to_owned(), unstable_room_account_data_events);
2233 client
2234 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2235 .await
2236 .expect("Failed to process sync");
2237
2238 assert_matches!(
2240 room_info_notable_update_stream.recv().await,
2241 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2242 assert_eq!(received_room_id, room_id);
2243 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2244 }
2245 );
2246 assert!(room_info_notable_update_stream.is_empty());
2247
2248 let stable_room_account_data_events = vec![
2251 Raw::from_json_string(
2252 json!({
2253 "type": "m.marked_unread",
2254 "event_id": "$3",
2255 "content": { "unread": true },
2256 "sender": client.session_meta().unwrap().user_id,
2257 "origin_server_ts": 12344445,
2258 })
2259 .to_string(),
2260 )
2261 .unwrap(),
2262 ];
2263 response
2264 .extensions
2265 .account_data
2266 .rooms
2267 .insert(room_id.to_owned(), stable_room_account_data_events);
2268 client
2269 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2270 .await
2271 .expect("Failed to process sync");
2272
2273 assert_matches!(
2275 room_info_notable_update_stream.recv().await,
2276 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2277 assert_eq!(received_room_id, room_id);
2278 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2279 }
2280 );
2281 assert!(room_info_notable_update_stream.is_empty());
2282 }
2283
2284 #[async_test]
2285 async fn test_pinned_events_are_updated_on_sync() {
2286 let user_a_id = user_id!("@a:e.uk");
2287 let client = logged_in_base_client(Some(user_a_id)).await;
2288 let room_id = room_id!("!r:e.uk");
2289 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2290
2291 let mut room_response = http::response::Room::new();
2293 set_room_joined(&mut room_response, user_a_id);
2294 let response = response_with_room(room_id, room_response);
2295 client
2296 .process_sliding_sync(&response, &RequestedRequiredStates::default())
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(&response, &RequestedRequiredStates::default())
2316 .await
2317 .expect("Failed to process sync");
2318
2319 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2320 assert_eq!(pinned_event_ids.len(), 1);
2321 assert_eq!(pinned_event_ids[0], pinned_event_id);
2322
2323 let mut room_response = http::response::Room::new();
2325 room_response.required_state.push(make_state_event(
2326 user_a_id,
2327 "",
2328 RoomPinnedEventsEventContent::new(Vec::new()),
2329 None,
2330 ));
2331 let response = response_with_room(room_id, room_response);
2332 client
2333 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2334 .await
2335 .expect("Failed to process sync");
2336 let pinned_event_ids = room.pinned_event_ids().unwrap();
2337 assert!(pinned_event_ids.is_empty());
2338 }
2339
2340 #[async_test]
2341 async fn test_dms_are_processed_in_any_sync_response() {
2342 let current_user_id = user_id!("@current:e.uk");
2343 let client = logged_in_base_client(Some(current_user_id)).await;
2344 let user_a_id = user_id!("@a:e.uk");
2345 let user_b_id = user_id!("@b:e.uk");
2346 let room_id_1 = room_id!("!r:e.uk");
2347 let room_id_2 = room_id!("!s:e.uk");
2348
2349 let mut room_response = http::response::Room::new();
2350 set_room_joined(&mut room_response, user_a_id);
2351 let mut response = response_with_room(room_id_1, room_response);
2352 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2353 BTreeMap::new();
2354 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2355 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2356 response
2357 .extensions
2358 .account_data
2359 .global
2360 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2361 client
2362 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2363 .await
2364 .expect("Failed to process sync");
2365
2366 let room_1 = client.get_room(room_id_1).unwrap();
2367 assert!(room_1.is_direct().await.unwrap());
2368
2369 let mut room_response = http::response::Room::new();
2371 set_room_joined(&mut room_response, user_b_id);
2372 let response = response_with_room(room_id_2, room_response);
2373 client
2374 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2375 .await
2376 .expect("Failed to process sync");
2377
2378 let room_2 = client.get_room(room_id_2).unwrap();
2379 assert!(room_2.is_direct().await.unwrap());
2380 }
2381
2382 #[async_test]
2383 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2384 let user_id = user_id!("@raclette:patate");
2385 let client = logged_in_base_client(Some(user_id)).await;
2386 let room_id_0 = room_id!("!r0");
2387 let room_id_1 = room_id!("!r1");
2388 let room_id_2 = room_id!("!r2");
2389
2390 let requested_required_states = RequestedRequiredStates::from(&{
2407 let mut request = http::Request::new();
2408
2409 request.room_subscriptions.insert(room_id_0.to_owned(), {
2410 let mut room_subscription = http::request::RoomSubscription::default();
2411
2412 room_subscription
2413 .required_state
2414 .push((StateEventType::RoomEncryption, "".to_owned()));
2415
2416 room_subscription
2417 });
2418
2419 request
2420 });
2421
2422 let mut response = http::Response::new("0".to_owned());
2423
2424 {
2428 let not_encrypted_room = http::response::Room::new();
2429 let mut encrypted_room = http::response::Room::new();
2430 set_room_is_encrypted(&mut encrypted_room, user_id);
2431
2432 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2433 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2434 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2435 }
2436
2437 client
2438 .process_sliding_sync(&response, &requested_required_states)
2439 .await
2440 .expect("Failed to process sync");
2441
2442 assert_matches!(
2444 client.get_room(room_id_0).unwrap().encryption_state(),
2445 EncryptionState::Encrypted
2446 );
2447 assert_matches!(
2448 client.get_room(room_id_1).unwrap().encryption_state(),
2449 EncryptionState::Encrypted
2450 );
2451 assert_matches!(
2453 client.get_room(room_id_2).unwrap().encryption_state(),
2454 EncryptionState::NotEncrypted
2455 )
2456 }
2457
2458 #[async_test]
2459 async fn test_room_encryption_state_is_unknown() {
2460 let user_id = user_id!("@raclette:patate");
2461 let client = logged_in_base_client(Some(user_id)).await;
2462 let room_id_0 = room_id!("!r0");
2463 let room_id_1 = room_id!("!r1");
2464
2465 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2478
2479 let mut response = http::Response::new("0".to_owned());
2480
2481 {
2483 let not_encrypted_room = http::response::Room::new();
2484 let mut encrypted_room = http::response::Room::new();
2485 set_room_is_encrypted(&mut encrypted_room, user_id);
2486
2487 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2488 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2489 }
2490
2491 client
2492 .process_sliding_sync(&response, &requested_required_states)
2493 .await
2494 .expect("Failed to process sync");
2495
2496 assert_matches!(
2499 client.get_room(room_id_0).unwrap().encryption_state(),
2500 EncryptionState::Encrypted
2501 );
2502 assert_matches!(
2505 client.get_room(room_id_1).unwrap().encryption_state(),
2506 EncryptionState::Unknown
2507 );
2508 }
2509
2510 #[cfg(feature = "e2e-encryption")]
2511 async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2512 let room = make_room();
2513 let mut room_info = room.clone_info();
2514 cache_latest_events(&room, &mut room_info, events, None, None).await;
2515 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2516 room.latest_event().map(|latest_event| latest_event.event().clone())
2517 }
2518
2519 #[cfg(feature = "e2e-encryption")]
2520 fn rawev_id(event: TimelineEvent) -> String {
2521 event.event_id().unwrap().to_string()
2522 }
2523
2524 fn ev_id(event: Option<TimelineEvent>) -> String {
2525 event.unwrap().event_id().unwrap().to_string()
2526 }
2527
2528 #[cfg(feature = "e2e-encryption")]
2529 fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2530 events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2531 }
2532
2533 #[cfg(feature = "e2e-encryption")]
2534 fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2535 events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2536 }
2537
2538 #[cfg(feature = "e2e-encryption")]
2539 fn make_room() -> Room {
2540 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2541
2542 Room::new(
2543 user_id!("@u:e.co"),
2544 Arc::new(MemoryStore::new()),
2545 room_id!("!r:e.co"),
2546 RoomState::Joined,
2547 sender,
2548 )
2549 }
2550
2551 fn make_raw_event(event_type: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2552 Raw::from_json_string(
2553 json!({
2554 "type": event_type,
2555 "event_id": id,
2556 "content": { "msgtype": "m.text", "body": "my msg" },
2557 "sender": "@u:h.uk",
2558 "origin_server_ts": 12344445,
2559 })
2560 .to_string(),
2561 )
2562 .unwrap()
2563 }
2564
2565 #[cfg(feature = "e2e-encryption")]
2566 fn make_event(event_type: &str, id: &str) -> TimelineEvent {
2567 TimelineEvent::from_plaintext(make_raw_event(event_type, id))
2568 }
2569
2570 #[cfg(feature = "e2e-encryption")]
2571 fn make_encrypted_event(id: &str) -> TimelineEvent {
2572 TimelineEvent::from_utd(
2573 Raw::from_json_string(
2574 json!({
2575 "type": "m.room.encrypted",
2576 "event_id": id,
2577 "content": {
2578 "algorithm": "m.megolm.v1.aes-sha2",
2579 "ciphertext": "",
2580 "sender_key": "",
2581 "device_id": "",
2582 "session_id": "",
2583 },
2584 "sender": "@u:h.uk",
2585 "origin_server_ts": 12344445,
2586 })
2587 .to_string(),
2588 )
2589 .unwrap(),
2590 UnableToDecryptInfo {
2591 session_id: Some("".to_owned()),
2592 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2593 },
2594 )
2595 }
2596
2597 async fn membership(
2598 client: &BaseClient,
2599 room_id: &RoomId,
2600 user_id: &UserId,
2601 ) -> MembershipState {
2602 let room = client.get_room(room_id).expect("Room not found!");
2603 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2604 member.membership().clone()
2605 }
2606
2607 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2608 let room = client.get_room(room_id).expect("Room not found!");
2609 room.direct_targets()
2610 }
2611
2612 async fn create_dm(
2615 client: &BaseClient,
2616 room_id: &RoomId,
2617 my_id: &UserId,
2618 their_id: &UserId,
2619 other_state: MembershipState,
2620 ) {
2621 let mut room = http::response::Room::new();
2622 set_room_joined(&mut room, my_id);
2623
2624 match other_state {
2625 MembershipState::Join => {
2626 room.joined_count = Some(uint!(2));
2627 room.invited_count = None;
2628 }
2629
2630 MembershipState::Invite => {
2631 room.joined_count = Some(uint!(1));
2632 room.invited_count = Some(uint!(1));
2633 }
2634
2635 _ => {
2636 room.joined_count = Some(uint!(1));
2637 room.invited_count = None;
2638 }
2639 }
2640
2641 room.required_state.push(make_membership_event(their_id, other_state));
2642
2643 let mut response = response_with_room(room_id, room);
2644 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2645 client
2646 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2647 .await
2648 .expect("Failed to process sync");
2649 }
2650
2651 async fn update_room_membership(
2653 client: &BaseClient,
2654 room_id: &RoomId,
2655 user_id: &UserId,
2656 new_state: MembershipState,
2657 ) {
2658 let mut room = http::response::Room::new();
2659 room.required_state.push(make_membership_event(user_id, new_state));
2660 let response = response_with_room(room_id, room);
2661 client
2662 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2663 .await
2664 .expect("Failed to process sync");
2665 }
2666
2667 fn set_direct_with(
2668 response: &mut http::Response,
2669 user_id: OwnedUserId,
2670 room_ids: Vec<OwnedRoomId>,
2671 ) {
2672 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2673 BTreeMap::new();
2674 direct_content.insert(user_id.into(), room_ids);
2675 response
2676 .extensions
2677 .account_data
2678 .global
2679 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2680 }
2681
2682 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2683 let mut response = http::Response::new("5".to_owned());
2684 response.rooms.insert(room_id.to_owned(), room);
2685 response
2686 }
2687
2688 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2689 let mut room = http::response::Room::new();
2690
2691 let mut avatar_event_content = RoomAvatarEventContent::new();
2692 avatar_event_content.url = Some(avatar_uri.to_owned());
2693
2694 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2695
2696 room
2697 }
2698
2699 fn room_with_canonical_alias(
2700 room_alias_id: &RoomAliasId,
2701 user_id: &UserId,
2702 ) -> http::response::Room {
2703 let mut room = http::response::Room::new();
2704
2705 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2706 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2707
2708 room.required_state.push(make_state_event(
2709 user_id,
2710 "",
2711 canonical_alias_event_content,
2712 None,
2713 ));
2714
2715 room
2716 }
2717
2718 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2719 let mut room = http::response::Room::new();
2720
2721 let name_event_content = RoomNameEventContent::new(name.to_owned());
2722
2723 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2724
2725 room
2726 }
2727
2728 fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2729 let mut room = http::response::Room::new();
2730 room.timeline.extend(
2731 events
2732 .iter()
2733 .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2734 .collect::<Vec<_>>(),
2735 );
2736 room
2737 }
2738
2739 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2740 room.required_state.push(make_state_event(
2741 sender,
2742 "",
2743 RoomNameEventContent::new(name),
2744 None,
2745 ));
2746 }
2747
2748 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2749 let evt = Raw::new(&json!({
2753 "type": "m.room.member",
2754 "sender": inviter,
2755 "content": {
2756 "is_direct": true,
2757 "membership": "invite",
2758 },
2759 "state_key": invitee,
2760 }))
2761 .expect("Failed to make raw event")
2762 .cast_unchecked();
2763
2764 room.invite_state = Some(vec![evt]);
2765
2766 room.required_state.push(make_state_event(
2769 inviter,
2770 invitee.as_str(),
2771 RoomMemberEventContent::new(MembershipState::Invite),
2772 None,
2773 ));
2774 }
2775
2776 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2777 let evt = Raw::new(&json!({
2781 "type": "m.room.member",
2782 "sender": knocker,
2783 "content": {
2784 "is_direct": true,
2785 "membership": "knock",
2786 },
2787 "state_key": knocker,
2788 }))
2789 .expect("Failed to make raw event")
2790 .cast_unchecked();
2791
2792 room.invite_state = Some(vec![evt]);
2793 }
2794
2795 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2796 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2797 }
2798
2799 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2800 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2801 }
2802
2803 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2804 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2805 }
2806
2807 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2808 room.required_state.push(make_encryption_event(user_id));
2809 }
2810
2811 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2812 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2813 }
2814
2815 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2816 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2817 }
2818
2819 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2820 Raw::new(&json!({
2821 "type": content.event_type(),
2822 "content": content,
2823 }))
2824 .expect("Failed to create account data event")
2825 .cast_unchecked()
2826 }
2827
2828 fn make_state_event<C: StateEventContent, E>(
2829 sender: &UserId,
2830 state_key: &str,
2831 content: C,
2832 prev_content: Option<C>,
2833 ) -> Raw<E> {
2834 let unsigned = if let Some(prev_content) = prev_content {
2835 json!({ "prev_content": prev_content })
2836 } else {
2837 json!({})
2838 };
2839
2840 Raw::new(&json!({
2841 "type": content.event_type(),
2842 "state_key": state_key,
2843 "content": content,
2844 "event_id": event_id!("$evt"),
2845 "sender": sender,
2846 "origin_server_ts": 10,
2847 "unsigned": unsigned,
2848 }))
2849 .expect("Failed to create state event")
2850 .cast_unchecked()
2851 }
2852}