1use matrix_sdk_common::deserialized_responses::TimelineEvent;
18use ruma::{api::client::sync::sync_events::v5 as http, OwnedRoomId};
19#[cfg(feature = "e2e-encryption")]
20use ruma::{events::AnyToDeviceEvent, serde::Raw};
21use tracing::{instrument, trace};
22
23use super::BaseClient;
24use crate::{
25 error::Result,
26 read_receipts::compute_unread_counts,
27 response_processors as processors,
28 room::RoomInfoNotableUpdateReasons,
29 store::ambiguity_map::AmbiguityCache,
30 sync::{RoomUpdates, SyncResponse},
31 RequestedRequiredStates,
32};
33
34impl BaseClient {
35 #[cfg(feature = "e2e-encryption")]
43 pub async fn process_sliding_sync_e2ee(
44 &self,
45 to_device: Option<&http::response::ToDevice>,
46 e2ee: &http::response::E2EE,
47 ) -> Result<Option<Vec<Raw<AnyToDeviceEvent>>>> {
48 if to_device.is_none() && e2ee.is_empty() {
49 return Ok(None);
50 }
51
52 trace!(
53 to_device_events =
54 to_device.map(|to_device| to_device.events.len()).unwrap_or_default(),
55 device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
56 device_unused_fallback_key_types =
57 e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
58 "Processing sliding sync e2ee events",
59 );
60
61 let olm_machine = self.olm_machine().await;
62
63 let mut context = processors::Context::default();
64
65 let processors::e2ee::to_device::Output { decrypted_to_device_events, room_key_updates } =
66 processors::e2ee::to_device::from_msc4186(to_device, e2ee, olm_machine.as_ref())
67 .await?;
68
69 processors::latest_event::decrypt_from_rooms(
70 &mut context,
71 room_key_updates
72 .into_iter()
73 .flatten()
74 .filter_map(|room_key_info| self.get_room(&room_key_info.room_id))
75 .collect(),
76 processors::e2ee::E2EE::new(
77 olm_machine.as_ref(),
78 self.decryption_trust_requirement,
79 self.handle_verification_events,
80 ),
81 )
82 .await?;
83
84 processors::changes::save_and_apply(
85 context,
86 &self.state_store,
87 &self.ignore_user_list_changes,
88 None,
89 )
90 .await?;
91
92 Ok(Some(decrypted_to_device_events))
93 }
94
95 #[instrument(skip_all, level = "trace")]
102 pub async fn process_sliding_sync(
103 &self,
104 response: &http::Response,
105 requested_required_states: &RequestedRequiredStates,
106 ) -> Result<SyncResponse> {
107 let http::Response { rooms, lists, extensions, .. } = response;
108
109 trace!(
110 rooms = rooms.len(),
111 lists = lists.len(),
112 has_extensions = !extensions.is_empty(),
113 "Processing sliding sync room events"
114 );
115
116 if rooms.is_empty() && extensions.is_empty() {
117 return Ok(SyncResponse::default());
120 };
121
122 let mut context = processors::Context::default();
123
124 let state_store = self.state_store.clone();
125 let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
126
127 let global_account_data_processor =
128 processors::account_data::global(&extensions.account_data.global);
129 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
130
131 let mut room_updates = RoomUpdates::default();
132 let mut notifications = Default::default();
133
134 let user_id = self
135 .session_meta()
136 .expect("Sliding sync shouldn't run without an authenticated user.")
137 .user_id
138 .to_owned();
139
140 for (room_id, room_response) in rooms {
141 let Some((room_info, room_update)) = processors::room::msc4186::update_any_room(
142 &mut context,
143 &user_id,
144 processors::room::RoomCreationData::new(
145 room_id,
146 self.room_info_notable_update_sender.clone(),
147 requested_required_states,
148 &mut ambiguity_cache,
149 ),
150 room_response,
151 &extensions.account_data.rooms,
152 #[cfg(feature = "e2e-encryption")]
153 processors::e2ee::E2EE::new(
154 self.olm_machine().await.as_ref(),
155 self.decryption_trust_requirement,
156 self.handle_verification_events,
157 ),
158 processors::notification::Notification::new(
159 &push_rules,
160 &mut notifications,
161 &self.state_store,
162 ),
163 )
164 .await?
165 else {
166 continue;
167 };
168
169 context.state_changes.add_room(room_info);
170
171 let room_id = room_id.to_owned();
172
173 use processors::room::msc4186::RoomUpdateKind;
174
175 match room_update {
176 RoomUpdateKind::Joined(joined_room_update) => {
177 room_updates.joined.insert(room_id, joined_room_update);
178 }
179 RoomUpdateKind::Left(left_room_update) => {
180 room_updates.left.insert(room_id, left_room_update);
181 }
182 RoomUpdateKind::Invited(invited_room_update) => {
183 room_updates.invited.insert(room_id, invited_room_update);
184 }
185 RoomUpdateKind::Knocked(knocked_room_update) => {
186 room_updates.knocked.insert(room_id, knocked_room_update);
187 }
188 }
189 }
190
191 processors::room::msc4186::extensions::dispatch_typing_ephemeral_events(
195 &extensions.typing,
196 &mut room_updates.joined,
197 );
198
199 processors::room::msc4186::extensions::room_account_data(
201 &mut context,
202 &extensions.account_data,
203 &mut room_updates,
204 &self.state_store,
205 )
206 .await;
207
208 global_account_data_processor.apply(&mut context, &state_store).await;
209
210 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
211
212 processors::changes::save_and_apply(
214 context,
215 &self.state_store,
216 &self.ignore_user_list_changes,
217 None,
218 )
219 .await?;
220
221 let mut context = processors::Context::default();
222
223 processors::room::display_name::update_for_rooms(
226 &mut context,
227 &room_updates,
228 &self.state_store,
229 )
230 .await;
231
232 processors::changes::save_only(context, &self.state_store).await?;
234
235 Ok(SyncResponse {
236 rooms: room_updates,
237 notifications,
238 presence: Default::default(),
239 account_data: extensions.account_data.global.clone(),
240 to_device: Default::default(),
241 })
242 }
243
244 #[doc(hidden)]
247 pub async fn process_sliding_sync_receipts_extension_for_room(
248 &self,
249 room_id: &OwnedRoomId,
250 response: &http::Response,
251 sync_response: &mut SyncResponse,
252 room_previous_events: Vec<TimelineEvent>,
253 ) -> Result<()> {
254 let mut context = processors::Context::default();
255
256 let mut save_context = false;
257
258 let joined_room_update = sync_response.rooms.joined.entry(room_id.to_owned()).or_default();
261
262 if let Some(receipt_ephemeral_event) = response.extensions.receipts.rooms.get(room_id) {
264 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
265 &mut context,
266 room_id,
267 receipt_ephemeral_event,
268 joined_room_update,
269 );
270 save_context = true;
271 }
272
273 let user_id = &self.session_meta().expect("logged in user").user_id;
274
275 if let Some(mut room_info) = self.get_room(room_id).map(|room| room.clone_info()) {
278 let prev_read_receipts = room_info.read_receipts.clone();
279
280 compute_unread_counts(
281 user_id,
282 room_id,
283 context.state_changes.receipts.get(room_id),
284 room_previous_events,
285 &joined_room_update.timeline.events,
286 &mut room_info.read_receipts,
287 );
288
289 if prev_read_receipts != room_info.read_receipts {
290 context
291 .room_info_notable_updates
292 .entry(room_id.clone())
293 .or_default()
294 .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
295
296 context.state_changes.add_room(room_info);
297 save_context = true;
298 }
299 }
300
301 if save_context {
303 processors::changes::save_only(context, &self.state_store).await?;
304 }
305
306 Ok(())
307 }
308}
309
310#[cfg(all(test, not(target_family = "wasm")))]
311mod tests {
312 use std::collections::{BTreeMap, HashSet};
313 #[cfg(feature = "e2e-encryption")]
314 use std::sync::{Arc, RwLock as SyncRwLock};
315
316 use assert_matches::assert_matches;
317 use matrix_sdk_common::deserialized_responses::TimelineEvent;
318 #[cfg(feature = "e2e-encryption")]
319 use matrix_sdk_common::{
320 deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
321 ring_buffer::RingBuffer,
322 };
323 use matrix_sdk_test::async_test;
324 use ruma::{
325 api::client::sync::sync_events::UnreadNotificationsCount,
326 assign, event_id,
327 events::{
328 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
329 room::{
330 avatar::RoomAvatarEventContent,
331 canonical_alias::RoomCanonicalAliasEventContent,
332 encryption::RoomEncryptionEventContent,
333 member::{MembershipState, RoomMemberEventContent},
334 message::SyncRoomMessageEvent,
335 name::RoomNameEventContent,
336 pinned_events::RoomPinnedEventsEventContent,
337 },
338 AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
339 StateEventContent, StateEventType,
340 },
341 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
342 serde::Raw,
343 uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
344 };
345 use serde_json::json;
346
347 use super::http;
348 #[cfg(feature = "e2e-encryption")]
349 use super::processors::room::msc4186::cache_latest_events;
350 use crate::{
351 room::{RoomHero, RoomInfoNotableUpdateReasons},
352 store::{RoomLoadSettings, StoreConfig},
353 test_utils::logged_in_base_client,
354 BaseClient, EncryptionState, RequestedRequiredStates, RoomInfoNotableUpdate, RoomState,
355 SessionMeta,
356 };
357 #[cfg(feature = "e2e-encryption")]
358 use crate::{store::MemoryStore, Room};
359
360 #[async_test]
361 async fn test_notification_count_set() {
362 let client = logged_in_base_client(None).await;
363
364 let mut response = http::Response::new("42".to_owned());
365 let room_id = room_id!("!room:example.org");
366 let count = assign!(UnreadNotificationsCount::default(), {
367 highlight_count: Some(uint!(13)),
368 notification_count: Some(uint!(37)),
369 });
370
371 response.rooms.insert(
372 room_id.to_owned(),
373 assign!(http::response::Room::new(), {
374 unread_notifications: count.clone()
375 }),
376 );
377
378 let sync_response = client
379 .process_sliding_sync(&response, &RequestedRequiredStates::default())
380 .await
381 .expect("Failed to process sync");
382
383 let room = sync_response.rooms.joined.get(room_id).unwrap();
385 assert_eq!(room.unread_notifications, count.clone().into());
386
387 let room = client.get_room(room_id).expect("found room");
389 assert_eq!(room.unread_notification_counts(), count.into());
390 }
391
392 #[async_test]
393 async fn test_can_process_empty_sliding_sync_response() {
394 let client = logged_in_base_client(None).await;
395 let empty_response = http::Response::new("5".to_owned());
396 client
397 .process_sliding_sync(&empty_response, &RequestedRequiredStates::default())
398 .await
399 .expect("Failed to process sync");
400 }
401
402 #[async_test]
403 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
404 let client = logged_in_base_client(None).await;
406 let room_id = room_id!("!r:e.uk");
407
408 let mut room = http::response::Room::new();
411 room.joined_count = Some(uint!(41));
412 let response = response_with_room(room_id, room);
413 let sync_resp = client
414 .process_sliding_sync(&response, &RequestedRequiredStates::default())
415 .await
416 .expect("Failed to process sync");
417
418 let client_room = client.get_room(room_id).expect("No room found");
420 assert_eq!(client_room.room_id(), room_id);
421 assert_eq!(client_room.joined_members_count(), 41);
422 assert_eq!(client_room.state(), RoomState::Joined);
423
424 assert!(sync_resp.rooms.joined.contains_key(room_id));
426 assert!(!sync_resp.rooms.left.contains_key(room_id));
427 assert!(!sync_resp.rooms.invited.contains_key(room_id));
428 }
429
430 #[async_test]
431 async fn test_missing_room_name_event() {
432 let client = logged_in_base_client(None).await;
434 let room_id = room_id!("!r:e.uk");
435
436 let mut room = http::response::Room::new();
439 room.name = Some("little room".to_owned());
440 let response = response_with_room(room_id, room);
441 let sync_resp = client
442 .process_sliding_sync(&response, &RequestedRequiredStates::default())
443 .await
444 .expect("Failed to process sync");
445
446 let client_room = client.get_room(room_id).expect("No room found");
448 assert!(client_room.name().is_none());
449 assert_eq!(
450 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
451 "Empty Room"
452 );
453 assert_eq!(client_room.state(), RoomState::Joined);
454
455 assert!(sync_resp.rooms.joined.contains_key(room_id));
457 assert!(!sync_resp.rooms.left.contains_key(room_id));
458 assert!(!sync_resp.rooms.invited.contains_key(room_id));
459 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
460 }
461
462 #[async_test]
463 async fn test_room_name_event() {
464 let client = logged_in_base_client(None).await;
466 let room_id = room_id!("!r:e.uk");
467
468 let mut room = http::response::Room::new();
471
472 room.name = Some("little room".to_owned());
473 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
474
475 let response = response_with_room(room_id, room);
476 client
477 .process_sliding_sync(&response, &RequestedRequiredStates::default())
478 .await
479 .expect("Failed to process sync");
480
481 let client_room = client.get_room(room_id).expect("No room found");
483 assert_eq!(client_room.name().as_deref(), Some("The Name"));
484 assert_eq!(
485 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
486 "The Name"
487 );
488 }
489
490 #[async_test]
491 async fn test_missing_invited_room_name_event() {
492 let client = logged_in_base_client(None).await;
494 let room_id = room_id!("!r:e.uk");
495 let user_id = user_id!("@w:e.uk");
496 let inviter = user_id!("@john:mastodon.org");
497
498 let mut room = http::response::Room::new();
501 set_room_invited(&mut room, inviter, user_id);
502 room.name = Some("name from sliding sync response".to_owned());
503 let response = response_with_room(room_id, room);
504 let sync_resp = client
505 .process_sliding_sync(&response, &RequestedRequiredStates::default())
506 .await
507 .expect("Failed to process sync");
508
509 let client_room = client.get_room(room_id).expect("No room found");
511 assert!(client_room.name().is_none());
512
513 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
515
516 assert_eq!(client_room.state(), RoomState::Invited);
517
518 assert!(!sync_resp.rooms.joined.contains_key(room_id));
520 assert!(!sync_resp.rooms.left.contains_key(room_id));
521 assert!(sync_resp.rooms.invited.contains_key(room_id));
522 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
523 }
524
525 #[async_test]
526 async fn test_invited_room_name_event() {
527 let client = logged_in_base_client(None).await;
529 let room_id = room_id!("!r:e.uk");
530 let user_id = user_id!("@w:e.uk");
531 let inviter = user_id!("@john:mastodon.org");
532
533 let mut room = http::response::Room::new();
536
537 set_room_invited(&mut room, inviter, user_id);
538
539 room.name = Some("name from sliding sync response".to_owned());
540 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
541
542 let response = response_with_room(room_id, room);
543 client
544 .process_sliding_sync(&response, &RequestedRequiredStates::default())
545 .await
546 .expect("Failed to process sync");
547
548 let client_room = client.get_room(room_id).expect("No room found");
550 assert_eq!(client_room.name().as_deref(), Some("The Name"));
551 assert_eq!(
552 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
553 "The Name"
554 );
555 }
556
557 #[async_test]
558 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
559 let client = logged_in_base_client(None).await;
561 let room_id = room_id!("!r:e.uk");
562 let user_id = client.session_meta().unwrap().user_id.to_owned();
563
564 let mut room = http::response::Room::new();
567 set_room_knocked(&mut room, &user_id);
568
569 let response = response_with_room(room_id, room);
570 client
571 .process_sliding_sync(&response, &RequestedRequiredStates::default())
572 .await
573 .expect("Failed to process sync");
574
575 let client_room = client.get_room(room_id).expect("No room found");
577 assert_eq!(client_room.state(), RoomState::Knocked);
578 }
579
580 #[async_test]
581 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
582 ) {
583 let client = logged_in_base_client(None).await;
585 let room_id = room_id!("!r:e.uk");
586 let user_id = user_id!("@w:e.uk");
587
588 let mut room = http::response::Room::new();
590 set_room_knocked(&mut room, user_id);
591
592 let response = response_with_room(room_id, room);
593 client
594 .process_sliding_sync(&response, &RequestedRequiredStates::default())
595 .await
596 .expect("Failed to process sync");
597
598 let client_room = client.get_room(room_id).expect("No room found");
601 assert_eq!(client_room.state(), RoomState::Invited);
602 }
603
604 #[async_test]
605 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
606 ) {
607 let client = logged_in_base_client(None).await;
609 let room_id = room_id!("!r:e.uk");
610 let user_id = client.session_meta().unwrap().user_id.to_owned();
611
612 let mut room = http::response::Room::new();
614 let event = Raw::new(&json!({
615 "type": "m.room.member",
616 "sender": user_id,
617 "content": {
618 "is_direct": true,
619 "membership": "join",
620 },
621 "state_key": user_id,
622 }))
623 .expect("Failed to make raw event")
624 .cast();
625 room.invite_state = Some(vec![event]);
626
627 let response = response_with_room(room_id, room);
628 client
629 .process_sliding_sync(&response, &RequestedRequiredStates::default())
630 .await
631 .expect("Failed to process sync");
632
633 let client_room = client.get_room(room_id).expect("No room found");
635 assert_eq!(client_room.state(), RoomState::Invited);
636 }
637
638 #[async_test]
639 async fn test_left_a_room_from_required_state_event() {
640 let client = logged_in_base_client(None).await;
642 let room_id = room_id!("!r:e.uk");
643 let user_id = user_id!("@u:e.uk");
644
645 let mut room = http::response::Room::new();
647 set_room_joined(&mut room, user_id);
648 let response = response_with_room(room_id, room);
649 client
650 .process_sliding_sync(&response, &RequestedRequiredStates::default())
651 .await
652 .expect("Failed to process sync");
653 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
654
655 let mut room = http::response::Room::new();
657 set_room_left(&mut room, user_id);
658 let response = response_with_room(room_id, room);
659 let sync_resp = client
660 .process_sliding_sync(&response, &RequestedRequiredStates::default())
661 .await
662 .expect("Failed to process sync");
663
664 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
666
667 assert!(!sync_resp.rooms.joined.contains_key(room_id));
669 assert!(sync_resp.rooms.left.contains_key(room_id));
670 assert!(!sync_resp.rooms.invited.contains_key(room_id));
671 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
672 }
673
674 #[async_test]
675 async fn test_kick_or_ban_updates_room_to_left() {
676 for membership in [MembershipState::Leave, MembershipState::Ban] {
677 let room_id = room_id!("!r:e.uk");
678 let user_a_id = user_id!("@a:e.uk");
679 let user_b_id = user_id!("@b:e.uk");
680 let client = logged_in_base_client(Some(user_a_id)).await;
681
682 let mut room = http::response::Room::new();
684 set_room_joined(&mut room, user_a_id);
685 let response = response_with_room(room_id, room);
686 client
687 .process_sliding_sync(&response, &RequestedRequiredStates::default())
688 .await
689 .expect("Failed to process sync");
690 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
691
692 let mut room = http::response::Room::new();
694 room.required_state.push(make_state_event(
695 user_b_id,
696 user_a_id.as_str(),
697 RoomMemberEventContent::new(membership.clone()),
698 None,
699 ));
700 let response = response_with_room(room_id, room);
701 let sync_resp = client
702 .process_sliding_sync(&response, &RequestedRequiredStates::default())
703 .await
704 .expect("Failed to process sync");
705
706 match membership {
707 MembershipState::Leave => {
708 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
710 }
711 MembershipState::Ban => {
712 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
714 }
715 _ => panic!("Unexpected membership state found: {membership}"),
716 }
717
718 assert!(!sync_resp.rooms.joined.contains_key(room_id));
720 assert!(sync_resp.rooms.left.contains_key(room_id));
721 assert!(!sync_resp.rooms.invited.contains_key(room_id));
722 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
723 }
724 }
725
726 #[async_test]
727 async fn test_left_a_room_from_timeline_state_event() {
728 let client = logged_in_base_client(None).await;
730 let room_id = room_id!("!r:e.uk");
731 let user_id = user_id!("@u:e.uk");
732
733 let mut room = http::response::Room::new();
735 set_room_joined(&mut room, user_id);
736 let response = response_with_room(room_id, room);
737 client
738 .process_sliding_sync(&response, &RequestedRequiredStates::default())
739 .await
740 .expect("Failed to process sync");
741 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
742
743 let mut room = http::response::Room::new();
745 set_room_left_as_timeline_event(&mut room, user_id);
746 let response = response_with_room(room_id, room);
747 client
748 .process_sliding_sync(&response, &RequestedRequiredStates::default())
749 .await
750 .expect("Failed to process sync");
751
752 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
754 }
755
756 #[async_test]
757 async fn test_can_be_reinvited_to_a_left_room() {
758 let client = logged_in_base_client(None).await;
762 let room_id = room_id!("!r:e.uk");
763 let user_id = user_id!("@u:e.uk");
764
765 let mut room = http::response::Room::new();
767 set_room_joined(&mut room, user_id);
768 let response = response_with_room(room_id, room);
769 client
770 .process_sliding_sync(&response, &RequestedRequiredStates::default())
771 .await
772 .expect("Failed to process sync");
773 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
775
776 let mut room = http::response::Room::new();
778 set_room_left(&mut room, user_id);
779 let response = response_with_room(room_id, room);
780 client
781 .process_sliding_sync(&response, &RequestedRequiredStates::default())
782 .await
783 .expect("Failed to process sync");
784 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
786
787 let mut room = http::response::Room::new();
789 set_room_invited(&mut room, user_id, user_id);
790 let response = response_with_room(room_id, room);
791 client
792 .process_sliding_sync(&response, &RequestedRequiredStates::default())
793 .await
794 .expect("Failed to process sync");
795
796 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
798 }
799
800 #[async_test]
801 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
802 let room_id = room_id!("!r:e.uk");
803 let user_a_id = user_id!("@a:e.uk");
804 let user_b_id = user_id!("@b:e.uk");
805
806 let client = logged_in_base_client(None).await;
808 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
809
810 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
812 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
813
814 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
816
817 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
821 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
822 }
823
824 #[async_test]
825 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
826 ) {
827 let room_id = room_id!("!r:e.uk");
828 let user_a_id = user_id!("@a:e.uk");
829 let user_b_id = user_id!("@b:e.uk");
830
831 let client = logged_in_base_client(None).await;
833 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
834
835 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
837 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
838
839 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
841
842 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
846 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
847 }
848
849 #[async_test]
850 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
851 let room_id = room_id!("!r:bar.org");
852 let user_a_id = user_id!("@a:bar.org");
853 let user_b_id = user_id!("@b:bar.org");
854
855 let client = logged_in_base_client(None).await;
857 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
858
859 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
861
862 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
864 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
865
866 let room = client.get_room(room_id).unwrap();
867
868 assert_eq!(room.active_members_count(), 2);
869 assert_eq!(room.joined_members_count(), 2);
870 assert_eq!(room.invited_members_count(), 0);
871 }
872
873 #[async_test]
874 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
875 let room_id = room_id!("!r:bar.org");
876 let user_a_id = user_id!("@a:bar.org");
877 let user_b_id = user_id!("@b:bar.org");
878
879 let client = logged_in_base_client(None).await;
881 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
882
883 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
885
886 assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
888 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
889
890 let room = client.get_room(room_id).unwrap();
891
892 assert_eq!(room.active_members_count(), 2);
893 assert_eq!(room.joined_members_count(), 1);
894 assert_eq!(room.invited_members_count(), 1);
895 }
896
897 #[async_test]
898 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
899 let client = logged_in_base_client(None).await;
901 let room_id = room_id!("!r:e.uk");
902
903 let room = {
905 let mut room = http::response::Room::new();
906 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
907
908 room
909 };
910 let response = response_with_room(room_id, room);
911 client
912 .process_sliding_sync(&response, &RequestedRequiredStates::default())
913 .await
914 .expect("Failed to process sync");
915
916 let client_room = client.get_room(room_id).expect("No room found");
918 assert_eq!(
919 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
920 "med1"
921 );
922 }
923
924 #[async_test]
925 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
926 let client = logged_in_base_client(None).await;
928 let room_id = room_id!("!r:e.uk");
929
930 let room = {
934 let mut room = http::response::Room::new();
935 room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
936
937 room
938 };
939 let response = response_with_room(room_id, room);
940 client
941 .process_sliding_sync(&response, &RequestedRequiredStates::default())
942 .await
943 .expect("Failed to process sync");
944
945 let client_room = client.get_room(room_id).expect("No room found");
947 assert_eq!(
948 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
949 "med1"
950 );
951
952 let room = http::response::Room::new();
956 let response = response_with_room(room_id, room);
957 client
958 .process_sliding_sync(&response, &RequestedRequiredStates::default())
959 .await
960 .expect("Failed to process sync");
961
962 let client_room = client.get_room(room_id).expect("No room found");
964 assert_eq!(
965 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
966 "med1"
967 );
968
969 let room = {
973 let mut room = http::response::Room::new();
974 room.avatar = JsOption::Null;
975
976 room
977 };
978 let response = response_with_room(room_id, room);
979 client
980 .process_sliding_sync(&response, &RequestedRequiredStates::default())
981 .await
982 .expect("Failed to process sync");
983
984 let client_room = client.get_room(room_id).expect("No room found");
986 assert!(client_room.avatar_url().is_none());
987 }
988
989 #[async_test]
990 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
991 let client = logged_in_base_client(None).await;
993 let room_id = room_id!("!r:e.uk");
994 let user_id = user_id!("@u:e.uk");
995
996 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
998 let response = response_with_room(room_id, room);
999 client
1000 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1001 .await
1002 .expect("Failed to process sync");
1003
1004 let client_room = client.get_room(room_id).expect("No room found");
1006 assert_eq!(
1007 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1008 "med1"
1009 );
1010 }
1011
1012 #[async_test]
1013 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1014 let client = logged_in_base_client(None).await;
1016 let room_id = room_id!("!r:e.uk");
1017 let user_id = user_id!("@u:e.uk");
1018
1019 let mut room = http::response::Room::new();
1021 set_room_invited(&mut room, user_id, user_id);
1022 let response = response_with_room(room_id, room);
1023 let sync_resp = 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!(client_room.room_id(), room_id);
1031 assert_eq!(client_room.state(), RoomState::Invited);
1032
1033 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1035 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1036 }
1037
1038 #[async_test]
1039 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1040 let client = logged_in_base_client(None).await;
1042 let room_id = room_id!("!r:e.uk");
1043 let user_id = user_id!("@u:e.uk");
1044
1045 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1047 set_room_invited(&mut room, user_id, user_id);
1048 let response = response_with_room(room_id, room);
1049 client
1050 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1051 .await
1052 .expect("Failed to process sync");
1053
1054 let client_room = client.get_room(room_id).expect("No room found");
1056 assert_eq!(
1057 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1058 "med1"
1059 );
1060 }
1061
1062 #[async_test]
1063 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
1064 ) {
1065 let client = logged_in_base_client(None).await;
1067 let room_id = room_id!("!r:e.uk");
1068 let user_id = user_id!("@u:e.uk");
1069 let room_alias_id = room_alias_id!("#myroom:e.uk");
1070
1071 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1073 set_room_invited(&mut room, user_id, user_id);
1074 let response = response_with_room(room_id, room);
1075 client
1076 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1077 .await
1078 .expect("Failed to process sync");
1079
1080 let client_room = client.get_room(room_id).expect("No room found");
1082 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1083 }
1084
1085 #[async_test]
1086 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1087 let client = logged_in_base_client(None).await;
1089 let room_id = room_id!("!r:e.uk");
1090 let user_id = user_id!("@u:e.uk");
1091 let room_alias_id = room_alias_id!("#myroom:e.uk");
1092
1093 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1096 room.name = Some("This came from the server".to_owned());
1097 let response = response_with_room(room_id, room);
1098 client
1099 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1100 .await
1101 .expect("Failed to process sync");
1102
1103 let client_room = client.get_room(room_id).expect("No room found");
1105 assert_eq!(
1106 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1107 "myroom"
1108 );
1109 assert!(client_room.name().is_none());
1110 }
1111
1112 #[async_test]
1113 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1114 let client = logged_in_base_client(None).await;
1115 let user_id = user_id!("@u:e.uk");
1116 let room_id = room_id!("!r:e.uk");
1117
1118 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1119
1120 let room = room_with_name("Hello World", user_id);
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 room = client.get_room(room_id).expect("No room found");
1128 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1129
1130 assert_matches!(
1131 room_info_notable_update.recv().await,
1132 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1133 assert_eq!(received_room_id, room_id);
1134 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1135 }
1136 );
1137 assert_matches!(
1138 room_info_notable_update.recv().await,
1139 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1140 assert_eq!(received_room_id, room_id);
1141 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1143 }
1144 );
1145 assert!(room_info_notable_update.is_empty());
1146 }
1147
1148 #[async_test]
1149 async fn test_display_name_is_persisted_from_sliding_sync() {
1150 let user_id = user_id!("@u:e.uk");
1151 let room_id = room_id!("!r:e.uk");
1152 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1153 let state_store;
1154
1155 {
1156 let client = {
1157 let store = StoreConfig::new("cross-process-foo".to_owned());
1158 state_store = store.state_store.clone();
1159
1160 let client = BaseClient::new(store);
1161 client
1162 .activate(
1163 session_meta.clone(),
1164 RoomLoadSettings::default(),
1165 #[cfg(feature = "e2e-encryption")]
1166 None,
1167 )
1168 .await
1169 .expect("`activate` failed!");
1170
1171 client
1172 };
1173
1174 let room = room_with_name("Hello World", user_id);
1177 let response = response_with_room(room_id, room);
1178 client
1179 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1180 .await
1181 .expect("Failed to process sync");
1182
1183 let room = client.get_room(room_id).expect("No room found");
1184 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1185 }
1186
1187 {
1188 let client = {
1189 let mut store = StoreConfig::new("cross-process-foo".to_owned());
1190 store.state_store = state_store;
1191 let client = BaseClient::new(store);
1192 client
1193 .activate(
1194 session_meta,
1195 RoomLoadSettings::default(),
1196 #[cfg(feature = "e2e-encryption")]
1197 None,
1198 )
1199 .await
1200 .expect("`activate` failed!");
1201
1202 client
1203 };
1204
1205 let room = client.get_room(room_id).expect("No room found");
1206 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1207 }
1208 }
1209
1210 #[async_test]
1211 async fn test_compute_heroes_from_sliding_sync() {
1212 let client = logged_in_base_client(None).await;
1214 let room_id = room_id!("!r:e.uk");
1215 let gordon = user_id!("@gordon:e.uk").to_owned();
1216 let alice = user_id!("@alice:e.uk").to_owned();
1217
1218 let mut room = http::response::Room::new();
1221 room.heroes = Some(vec![
1222 assign!(http::response::Hero::new(gordon), {
1223 name: Some("Gordon".to_owned()),
1224 }),
1225 assign!(http::response::Hero::new(alice), {
1226 name: Some("Alice".to_owned()),
1227 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1228 }),
1229 ]);
1230 let response = response_with_room(room_id, room);
1231 let _sync_resp = client
1232 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1233 .await
1234 .expect("Failed to process sync");
1235
1236 let client_room = client.get_room(room_id).expect("No room found");
1238 assert_eq!(client_room.room_id(), room_id);
1239 assert_eq!(client_room.state(), RoomState::Joined);
1240
1241 assert_eq!(
1243 client_room.clone_info().summary.heroes(),
1244 &[
1245 RoomHero {
1246 user_id: owned_user_id!("@gordon:e.uk"),
1247 display_name: Some("Gordon".to_owned()),
1248 avatar_url: None
1249 },
1250 RoomHero {
1251 user_id: owned_user_id!("@alice:e.uk"),
1252 display_name: Some("Alice".to_owned()),
1253 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1254 },
1255 ]
1256 );
1257 }
1258
1259 #[async_test]
1260 async fn test_last_event_from_sliding_sync_is_cached() {
1261 let client = logged_in_base_client(None).await;
1263 let room_id = room_id!("!r:e.uk");
1264 let event_a = json!({
1265 "sender":"@alice:example.com",
1266 "type":"m.room.message",
1267 "event_id": "$ida",
1268 "origin_server_ts": 12344446,
1269 "content":{"body":"A", "msgtype": "m.text"}
1270 });
1271 let event_b = json!({
1272 "sender":"@alice:example.com",
1273 "type":"m.room.message",
1274 "event_id": "$idb",
1275 "origin_server_ts": 12344447,
1276 "content":{"body":"B", "msgtype": "m.text"}
1277 });
1278
1279 let events = &[event_a, event_b.clone()];
1281 let room = room_with_timeline(events);
1282 let response = response_with_room(room_id, room);
1283 client
1284 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1285 .await
1286 .expect("Failed to process sync");
1287
1288 let client_room = client.get_room(room_id).expect("No room found");
1290 assert_eq!(
1291 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1292 "$idb"
1293 );
1294 }
1295
1296 #[async_test]
1297 async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1298 let own_user_id = user_id!("@me:e.uk");
1299 let client = logged_in_base_client(Some(own_user_id)).await;
1301 let room_id = room_id!("!r:e.uk");
1302
1303 let power_levels = json!({
1305 "sender":"@alice:example.com",
1306 "state_key":"",
1307 "type":"m.room.power_levels",
1308 "event_id": "$idb",
1309 "origin_server_ts": 12344445,
1310 "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1311 "room_id": room_id,
1312 });
1313
1314 let knock_event = json!({
1316 "sender":"@alice:example.com",
1317 "state_key":"@alice:example.com",
1318 "type":"m.room.member",
1319 "event_id": "$ida",
1320 "origin_server_ts": 12344446,
1321 "content":{"membership": "knock"},
1322 "room_id": room_id,
1323 });
1324
1325 let events = &[knock_event];
1327 let mut room = room_with_timeline(events);
1328 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1329 let response = response_with_room(room_id, room);
1330 client
1331 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1332 .await
1333 .expect("Failed to process sync");
1334
1335 let client_room = client.get_room(room_id).expect("No room found");
1337 assert_eq!(
1338 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1339 "$ida"
1340 );
1341 }
1342
1343 #[async_test]
1344 async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1345 let own_user_id = user_id!("@me:e.uk");
1346 let client = logged_in_base_client(Some(own_user_id)).await;
1348 let room_id = room_id!("!r:e.uk");
1349
1350 let power_levels = json!({
1353 "sender":"@alice:example.com",
1354 "state_key":"",
1355 "type":"m.room.power_levels",
1356 "event_id": "$idb",
1357 "origin_server_ts": 12344445,
1358 "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1359 "room_id": room_id,
1360 });
1361
1362 let knock_event = json!({
1364 "sender":"@alice:example.com",
1365 "state_key":"@alice:example.com",
1366 "type":"m.room.member",
1367 "event_id": "$ida",
1368 "origin_server_ts": 12344446,
1369 "content":{"membership": "knock"},
1370 "room_id": room_id,
1371 });
1372
1373 let events = &[knock_event];
1375 let mut room = room_with_timeline(events);
1376 room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1377 let response = response_with_room(room_id, room);
1378 client
1379 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1380 .await
1381 .expect("Failed to process sync");
1382
1383 let client_room = client.get_room(room_id).expect("No room found");
1385 assert!(client_room.latest_event().is_none());
1386 }
1387
1388 #[async_test]
1389 async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1390 let client = logged_in_base_client(None).await;
1392 let room_id = room_id!("!r:e.uk");
1393 let join_event = json!({
1395 "sender":"@alice:example.com",
1396 "state_key":"@alice:example.com",
1397 "type":"m.room.member",
1398 "event_id": "$ida",
1399 "origin_server_ts": 12344446,
1400 "content":{"membership": "join"},
1401 "room_id": room_id,
1402 });
1403
1404 let events = &[join_event];
1406 let room = room_with_timeline(events);
1407 let response = response_with_room(room_id, room);
1408 client
1409 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1410 .await
1411 .expect("Failed to process sync");
1412
1413 let client_room = client.get_room(room_id).expect("No room found");
1415 assert!(client_room.latest_event().is_none());
1416 }
1417
1418 #[async_test]
1419 async fn test_cached_latest_event_can_be_redacted() {
1420 let client = logged_in_base_client(None).await;
1422 let room_id = room_id!("!r:e.uk");
1423 let event_a = json!({
1424 "sender": "@alice:example.com",
1425 "type": "m.room.message",
1426 "event_id": "$ida",
1427 "origin_server_ts": 12344446,
1428 "content": { "body":"A", "msgtype": "m.text" },
1429 });
1430
1431 let room = room_with_timeline(&[event_a]);
1433 let response = response_with_room(room_id, room);
1434 client
1435 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1436 .await
1437 .expect("Failed to process sync");
1438
1439 let client_room = client.get_room(room_id).expect("No room found");
1441 assert_eq!(
1442 ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1443 "$ida"
1444 );
1445
1446 let redaction = json!({
1447 "sender": "@alice:example.com",
1448 "type": "m.room.redaction",
1449 "event_id": "$idb",
1450 "redacts": "$ida",
1451 "origin_server_ts": 12344448,
1452 "content": {},
1453 });
1454
1455 let room = room_with_timeline(&[redaction]);
1457 let response = response_with_room(room_id, room);
1458 client
1459 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1460 .await
1461 .expect("Failed to process sync");
1462
1463 let client_room = client.get_room(room_id).expect("No room found");
1465 let latest_event = client_room.latest_event().unwrap();
1466 assert_eq!(latest_event.event_id().unwrap(), "$ida");
1467
1468 assert_matches!(
1470 latest_event.event().raw().deserialize().unwrap(),
1471 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1472 SyncRoomMessageEvent::Redacted(_)
1473 ))
1474 );
1475 }
1476
1477 #[cfg(feature = "e2e-encryption")]
1478 #[async_test]
1479 async fn test_when_no_events_we_dont_cache_any() {
1480 let events = &[];
1481 let chosen = choose_event_to_cache(events).await;
1482 assert!(chosen.is_none());
1483 }
1484
1485 #[cfg(feature = "e2e-encryption")]
1486 #[async_test]
1487 async fn test_when_only_one_event_we_cache_it() {
1488 let event1 = make_event("m.room.message", "$1");
1489 let events = &[event1.clone()];
1490 let chosen = choose_event_to_cache(events).await;
1491 assert_eq!(ev_id(chosen), rawev_id(event1));
1492 }
1493
1494 #[cfg(feature = "e2e-encryption")]
1495 #[async_test]
1496 async fn test_with_multiple_events_we_cache_the_last_one() {
1497 let event1 = make_event("m.room.message", "$1");
1498 let event2 = make_event("m.room.message", "$2");
1499 let events = &[event1, event2.clone()];
1500 let chosen = choose_event_to_cache(events).await;
1501 assert_eq!(ev_id(chosen), rawev_id(event2));
1502 }
1503
1504 #[cfg(feature = "e2e-encryption")]
1505 #[async_test]
1506 async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1507 let event1 = make_event("m.room.message", "$1");
1508 let event2 = make_event("m.room.message", "$2");
1509 let event3 = make_event("m.room.powerlevels", "$3");
1510 let event4 = make_event("m.room.powerlevels", "$5");
1511 let events = &[event1, event2.clone(), event3, event4];
1512 let chosen = choose_event_to_cache(events).await;
1513 assert_eq!(ev_id(chosen), rawev_id(event2));
1514 }
1515
1516 #[cfg(feature = "e2e-encryption")]
1517 #[async_test]
1518 async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1519 let event1 = make_event("m.room.power_levels", "$1");
1520 let events = &[event1];
1521 let chosen = choose_event_to_cache(events).await;
1522 assert!(chosen.is_none());
1523 }
1524
1525 #[cfg(feature = "e2e-encryption")]
1526 #[async_test]
1527 async fn test_cache_encrypted_events_that_are_after_latest_message() {
1528 let event1 = make_event("m.room.message", "$1");
1530 let event2 = make_event("m.room.message", "$2");
1531 let event3 = make_encrypted_event("$3");
1532 let event4 = make_encrypted_event("$4");
1533 let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
1534
1535 let room = make_room();
1537 let mut room_info = room.clone_info();
1538 cache_latest_events(&room, &mut room_info, events, None, None).await;
1539
1540 assert_eq!(
1542 ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
1543 rawev_id(event2.clone())
1544 );
1545
1546 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1547 assert_eq!(
1548 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1549 rawev_id(event2)
1550 );
1551
1552 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
1554 }
1555
1556 #[cfg(feature = "e2e-encryption")]
1557 #[async_test]
1558 async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
1559 let event1 = make_encrypted_event("$1");
1561 let event2 = make_event("m.room.message", "$2");
1562 let event3 = make_encrypted_event("$3");
1563 let events = &[event1, event2.clone(), event3.clone()];
1564
1565 let room = make_room();
1567 let mut room_info = room.clone_info();
1568 cache_latest_events(&room, &mut room_info, events, None, None).await;
1569 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1570
1571 assert_eq!(
1573 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1574 rawev_id(event2)
1575 );
1576
1577 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
1579 }
1580
1581 #[cfg(feature = "e2e-encryption")]
1582 #[async_test]
1583 async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
1584 let event1 = make_event("m.room.message", "$1");
1587 let event2 = make_event("m.room.message", "$2");
1588 let event3 = make_encrypted_event("$3");
1589 let event4 = make_event("m.read", "$4");
1590 let event5 = make_encrypted_event("$5");
1591 let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
1592
1593 let room = make_room();
1595 let mut room_info = room.clone_info();
1596 cache_latest_events(&room, &mut room_info, events, None, None).await;
1597 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1598
1599 assert_eq!(
1601 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1602 rawev_id(event2)
1603 );
1604
1605 assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
1607 }
1608
1609 #[cfg(feature = "e2e-encryption")]
1610 #[async_test]
1611 async fn test_only_store_the_max_number_of_encrypted_events() {
1612 let evente = make_event("m.room.message", "$e");
1615 let eventd = make_event("m.room.message", "$d");
1616 let eventc = make_encrypted_event("$c");
1617 let event9 = make_encrypted_event("$9");
1618 let event8 = make_encrypted_event("$8");
1619 let event7 = make_encrypted_event("$7");
1620 let eventb = make_event("m.read", "$b");
1621 let event6 = make_encrypted_event("$6");
1622 let event5 = make_encrypted_event("$5");
1623 let event4 = make_encrypted_event("$4");
1624 let event3 = make_encrypted_event("$3");
1625 let event2 = make_encrypted_event("$2");
1626 let eventa = make_event("m.read", "$a");
1627 let event1 = make_encrypted_event("$1");
1628 let event0 = make_encrypted_event("$0");
1629 let events = &[
1630 evente,
1631 eventd.clone(),
1632 eventc,
1633 event9.clone(),
1634 event8.clone(),
1635 event7.clone(),
1636 eventb,
1637 event6.clone(),
1638 event5.clone(),
1639 event4.clone(),
1640 event3.clone(),
1641 event2.clone(),
1642 eventa,
1643 event1.clone(),
1644 event0.clone(),
1645 ];
1646
1647 let room = make_room();
1649 let mut room_info = room.clone_info();
1650 cache_latest_events(&room, &mut room_info, events, None, None).await;
1651 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1652
1653 assert_eq!(
1655 ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1656 rawev_id(eventd)
1657 );
1658
1659 assert_eq!(
1661 rawevs_ids(&room.latest_encrypted_events),
1662 evs_ids(&[
1663 event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
1664 ])
1665 );
1666 }
1667
1668 #[cfg(feature = "e2e-encryption")]
1669 #[async_test]
1670 async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
1671 let room = make_room();
1673 let mut room_info = room.clone_info();
1674 cache_latest_events(
1675 &room,
1676 &mut room_info,
1677 &[
1678 make_encrypted_event("$0"),
1679 make_encrypted_event("$1"),
1680 make_encrypted_event("$2"),
1681 make_encrypted_event("$3"),
1682 make_encrypted_event("$4"),
1683 make_encrypted_event("$5"),
1684 make_encrypted_event("$6"),
1685 make_encrypted_event("$7"),
1686 make_encrypted_event("$8"),
1687 make_encrypted_event("$9"),
1688 ],
1689 None,
1690 None,
1691 )
1692 .await;
1693 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1694
1695 assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
1697
1698 let eventa = make_encrypted_event("$a");
1700 let mut room_info = room.clone_info();
1701 cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
1702 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1703
1704 assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
1706
1707 assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
1709 }
1710
1711 #[cfg(feature = "e2e-encryption")]
1712 #[async_test]
1713 async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
1714 let room = make_room();
1716 let mut room_info = room.clone_info();
1717 cache_latest_events(
1718 &room,
1719 &mut room_info,
1720 &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
1721 None,
1722 None,
1723 )
1724 .await;
1725 room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
1726
1727 let eventa = make_event("m.room.message", "$a");
1729 let eventb = make_encrypted_event("$b");
1730 cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
1731 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1732
1733 assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
1735
1736 assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
1738 }
1739
1740 #[async_test]
1741 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
1742 let client = logged_in_base_client(None).await;
1744 let room_id = room_id!("!r:e.uk");
1745
1746 let room = assign!(http::response::Room::new(), {
1748 bump_stamp: Some(42u32.into()),
1749 });
1750 let response = response_with_room(room_id, room);
1751 client
1752 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1753 .await
1754 .expect("Failed to process sync");
1755
1756 let client_room = client.get_room(room_id).expect("No room found");
1758 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1759 }
1760
1761 #[async_test]
1762 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
1763 let client = logged_in_base_client(None).await;
1765 let room_id = room_id!("!r:e.uk");
1766
1767 {
1768 let room = assign!(http::response::Room::new(), {
1770 bump_stamp: Some(42u32.into()),
1771 });
1772 let response = response_with_room(room_id, room);
1773 client
1774 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1775 .await
1776 .expect("Failed to process sync");
1777
1778 let client_room = client.get_room(room_id).expect("No room found");
1780 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1781 }
1782
1783 {
1784 let room = assign!(http::response::Room::new(), {
1786 bump_stamp: None,
1787 });
1788 let response = response_with_room(room_id, room);
1789 client
1790 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1791 .await
1792 .expect("Failed to process sync");
1793
1794 let client_room = client.get_room(room_id).expect("No room found");
1796 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
1797 }
1798
1799 {
1800 let room = assign!(http::response::Room::new(), {
1803 bump_stamp: Some(153u32.into()),
1804 });
1805 let response = response_with_room(room_id, room);
1806 client
1807 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1808 .await
1809 .expect("Failed to process sync");
1810
1811 let client_room = client.get_room(room_id).expect("No room found");
1813 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
1814 }
1815 }
1816
1817 #[async_test]
1818 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
1819 let client = logged_in_base_client(None).await;
1821 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1822 let room_id = room_id!("!r:e.uk");
1823
1824 let room = assign!(http::response::Room::new(), {
1826 bump_stamp: Some(42u32.into()),
1827 });
1828 let response = response_with_room(room_id, room);
1829 client
1830 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1831 .await
1832 .expect("Failed to process sync");
1833
1834 assert_matches!(
1837 room_info_notable_update_stream.recv().await,
1838 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1839 assert_eq!(received_room_id, room_id);
1840 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1841 }
1842 );
1843 assert_matches!(
1844 room_info_notable_update_stream.recv().await,
1845 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1846 assert_eq!(received_room_id, room_id);
1847 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1848 }
1849 );
1850 assert!(room_info_notable_update_stream.is_empty());
1851
1852 let room = assign!(http::response::Room::new(), {
1854 bump_stamp: Some(43u32.into()),
1855 });
1856 let response = response_with_room(room_id, room);
1857 client
1858 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1859 .await
1860 .expect("Failed to process sync");
1861
1862 assert_matches!(
1864 room_info_notable_update_stream.recv().await,
1865 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1866 assert_eq!(received_room_id, room_id);
1867 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
1868 }
1869 );
1870 assert!(room_info_notable_update_stream.is_empty());
1871 }
1872
1873 #[async_test]
1874 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
1875 let client = logged_in_base_client(None).await;
1877 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1878
1879 let room_id = room_id!("!r:e.uk");
1881 let room = http::response::Room::new();
1882 let response = response_with_room(room_id, room);
1883 client
1884 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1885 .await
1886 .expect("Failed to process sync");
1887
1888 assert_matches!(
1890 room_info_notable_update_stream.recv().await,
1891 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1892 assert_eq!(received_room_id, room_id);
1893 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1894 }
1895 );
1896 assert_matches!(
1897 room_info_notable_update_stream.recv().await,
1898 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1899 assert_eq!(received_room_id, room_id);
1900 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1901 }
1902 );
1903
1904 let room_id = room_id!("!r:e.uk");
1906 let events = vec![Raw::from_json_string(
1907 json!({
1908 "type": "m.room.member",
1909 "event_id": "$3",
1910 "content": { "membership": "join" },
1911 "sender": "@u:h.uk",
1912 "origin_server_ts": 12344445,
1913 "state_key": "@u:e.uk",
1914 })
1915 .to_string(),
1916 )
1917 .unwrap()];
1918 let room = assign!(http::response::Room::new(), {
1919 required_state: events,
1920 });
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!(room_info_notable_update_stream.is_empty());
1936
1937 let events = vec![Raw::from_json_string(
1938 json!({
1939 "type": "m.room.member",
1940 "event_id": "$3",
1941 "content": { "membership": "leave" },
1942 "sender": "@u:h.uk",
1943 "origin_server_ts": 12344445,
1944 "state_key": "@u:e.uk",
1945 })
1946 .to_string(),
1947 )
1948 .unwrap()];
1949 let room = assign!(http::response::Room::new(), {
1950 required_state: events,
1951 });
1952 let response = response_with_room(room_id, room);
1953 client
1954 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1955 .await
1956 .expect("Failed to process sync");
1957
1958 assert_matches!(
1960 room_info_notable_update_stream.recv().await,
1961 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1962 assert_eq!(received_room_id, room_id);
1963 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1964 }
1965 );
1966 assert!(room_info_notable_update_stream.is_empty());
1967 }
1968
1969 #[async_test]
1970 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
1971 let client = logged_in_base_client(None).await;
1973 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
1974
1975 let room_id = room_id!("!r:e.uk");
1977 let room = http::response::Room::new();
1978 let response = response_with_room(room_id, room);
1979 client
1980 .process_sliding_sync(&response, &RequestedRequiredStates::default())
1981 .await
1982 .expect("Failed to process sync");
1983
1984 assert_matches!(
1986 room_info_notable_update_stream.recv().await,
1987 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1988 assert_eq!(received_room_id, room_id);
1989 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
1990 }
1991 );
1992 assert_matches!(
1993 room_info_notable_update_stream.recv().await,
1994 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
1995 assert_eq!(received_room_id, room_id);
1996 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
1997 }
1998 );
1999 assert!(room_info_notable_update_stream.is_empty());
2000
2001 let room_id = room_id!("!r:e.uk");
2004 let room_account_data_events = vec![Raw::from_json_string(
2005 json!({
2006 "type": "m.marked_unread",
2007 "event_id": "$1",
2008 "content": { "unread": true },
2009 "sender": client.session_meta().unwrap().user_id,
2010 "origin_server_ts": 12344445,
2011 })
2012 .to_string(),
2013 )
2014 .unwrap()];
2015 let mut response = response_with_room(room_id, http::response::Room::new());
2016 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2017
2018 client
2019 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2020 .await
2021 .expect("Failed to process sync");
2022
2023 assert_matches!(
2025 room_info_notable_update_stream.recv().await,
2026 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2027 assert_eq!(received_room_id, room_id);
2028 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2029 }
2030 );
2031
2032 client
2034 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2035 .await
2036 .expect("Failed to process sync");
2037
2038 assert_matches!(
2039 room_info_notable_update_stream.recv().await,
2040 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2041 assert_eq!(received_room_id, room_id);
2042 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2043 }
2044 );
2045 assert!(room_info_notable_update_stream.is_empty());
2046
2047 let room_account_data_events = vec![Raw::from_json_string(
2049 json!({
2050 "type": "m.marked_unread",
2051 "event_id": "$1",
2052 "content": { "unread": false },
2053 "sender": client.session_meta().unwrap().user_id,
2054 "origin_server_ts": 12344445,
2055 })
2056 .to_string(),
2057 )
2058 .unwrap()];
2059 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2060 client
2061 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2062 .await
2063 .expect("Failed to process sync");
2064
2065 assert_matches!(
2066 room_info_notable_update_stream.recv().await,
2067 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2068 assert_eq!(received_room_id, room_id);
2069 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2070 }
2071 );
2072 assert!(room_info_notable_update_stream.is_empty());
2073 }
2074
2075 #[async_test]
2076 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2077 let client = logged_in_base_client(None).await;
2079 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2080
2081 let room_id = room_id!("!r:e.uk");
2083 let room = http::response::Room::new();
2084 let response = response_with_room(room_id, room);
2085 client
2086 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2087 .await
2088 .expect("Failed to process sync");
2089
2090 assert_matches!(
2092 room_info_notable_update_stream.recv().await,
2093 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2094 assert_eq!(received_room_id, room_id);
2095 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2096 }
2097 );
2098 assert_matches!(
2099 room_info_notable_update_stream.recv().await,
2100 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2101 assert_eq!(received_room_id, room_id);
2102 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2103 }
2104 );
2105 assert!(room_info_notable_update_stream.is_empty());
2106
2107 let room_id = room_id!("!r:e.uk");
2110 let unstable_room_account_data_events = vec![Raw::from_json_string(
2111 json!({
2112 "type": "com.famedly.marked_unread",
2113 "event_id": "$1",
2114 "content": { "unread": true },
2115 "sender": client.session_meta().unwrap().user_id,
2116 "origin_server_ts": 12344445,
2117 })
2118 .to_string(),
2119 )
2120 .unwrap()];
2121 let mut response = response_with_room(room_id, http::response::Room::new());
2122 response
2123 .extensions
2124 .account_data
2125 .rooms
2126 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2127
2128 client
2129 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2130 .await
2131 .expect("Failed to process sync");
2132
2133 assert_matches!(
2135 room_info_notable_update_stream.recv().await,
2136 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2137 assert_eq!(received_room_id, room_id);
2138 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2139 }
2140 );
2141 assert!(room_info_notable_update_stream.is_empty());
2142
2143 let stable_room_account_data_events = vec![Raw::from_json_string(
2145 json!({
2146 "type": "m.marked_unread",
2147 "event_id": "$1",
2148 "content": { "unread": false },
2149 "sender": client.session_meta().unwrap().user_id,
2150 "origin_server_ts": 12344445,
2151 })
2152 .to_string(),
2153 )
2154 .unwrap()];
2155 response
2156 .extensions
2157 .account_data
2158 .rooms
2159 .insert(room_id.to_owned(), stable_room_account_data_events);
2160 client
2161 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2162 .await
2163 .expect("Failed to process sync");
2164
2165 assert_matches!(
2167 room_info_notable_update_stream.recv().await,
2168 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2169 assert_eq!(received_room_id, room_id);
2170 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2171 }
2172 );
2173 assert!(room_info_notable_update_stream.is_empty());
2174
2175 response
2178 .extensions
2179 .account_data
2180 .rooms
2181 .insert(room_id.to_owned(), unstable_room_account_data_events);
2182 client
2183 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2184 .await
2185 .expect("Failed to process sync");
2186
2187 assert_matches!(
2189 room_info_notable_update_stream.recv().await,
2190 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2191 assert_eq!(received_room_id, room_id);
2192 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2193 }
2194 );
2195 assert!(room_info_notable_update_stream.is_empty());
2196
2197 let stable_room_account_data_events = vec![Raw::from_json_string(
2200 json!({
2201 "type": "m.marked_unread",
2202 "event_id": "$3",
2203 "content": { "unread": true },
2204 "sender": client.session_meta().unwrap().user_id,
2205 "origin_server_ts": 12344445,
2206 })
2207 .to_string(),
2208 )
2209 .unwrap()];
2210 response
2211 .extensions
2212 .account_data
2213 .rooms
2214 .insert(room_id.to_owned(), stable_room_account_data_events);
2215 client
2216 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2217 .await
2218 .expect("Failed to process sync");
2219
2220 assert_matches!(
2222 room_info_notable_update_stream.recv().await,
2223 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2224 assert_eq!(received_room_id, room_id);
2225 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2226 }
2227 );
2228 assert!(room_info_notable_update_stream.is_empty());
2229 }
2230
2231 #[async_test]
2232 async fn test_pinned_events_are_updated_on_sync() {
2233 let user_a_id = user_id!("@a:e.uk");
2234 let client = logged_in_base_client(Some(user_a_id)).await;
2235 let room_id = room_id!("!r:e.uk");
2236 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2237
2238 let mut room_response = http::response::Room::new();
2240 set_room_joined(&mut room_response, user_a_id);
2241 let response = response_with_room(room_id, room_response);
2242 client
2243 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2244 .await
2245 .expect("Failed to process sync");
2246
2247 let room = client.get_room(room_id).unwrap();
2249 let pinned_event_ids = room.pinned_event_ids();
2250 assert_matches!(pinned_event_ids, None);
2251
2252 let mut room_response = http::response::Room::new();
2254 room_response.required_state.push(make_state_event(
2255 user_a_id,
2256 "",
2257 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2258 None,
2259 ));
2260 let response = response_with_room(room_id, room_response);
2261 client
2262 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2263 .await
2264 .expect("Failed to process sync");
2265
2266 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2267 assert_eq!(pinned_event_ids.len(), 1);
2268 assert_eq!(pinned_event_ids[0], pinned_event_id);
2269
2270 let mut room_response = http::response::Room::new();
2272 room_response.required_state.push(make_state_event(
2273 user_a_id,
2274 "",
2275 RoomPinnedEventsEventContent::new(Vec::new()),
2276 None,
2277 ));
2278 let response = response_with_room(room_id, room_response);
2279 client
2280 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2281 .await
2282 .expect("Failed to process sync");
2283 let pinned_event_ids = room.pinned_event_ids().unwrap();
2284 assert!(pinned_event_ids.is_empty());
2285 }
2286
2287 #[async_test]
2288 async fn test_dms_are_processed_in_any_sync_response() {
2289 let current_user_id = user_id!("@current:e.uk");
2290 let client = logged_in_base_client(Some(current_user_id)).await;
2291 let user_a_id = user_id!("@a:e.uk");
2292 let user_b_id = user_id!("@b:e.uk");
2293 let room_id_1 = room_id!("!r:e.uk");
2294 let room_id_2 = room_id!("!s:e.uk");
2295
2296 let mut room_response = http::response::Room::new();
2297 set_room_joined(&mut room_response, user_a_id);
2298 let mut response = response_with_room(room_id_1, room_response);
2299 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2300 BTreeMap::new();
2301 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2302 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2303 response
2304 .extensions
2305 .account_data
2306 .global
2307 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2308 client
2309 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2310 .await
2311 .expect("Failed to process sync");
2312
2313 let room_1 = client.get_room(room_id_1).unwrap();
2314 assert!(room_1.is_direct().await.unwrap());
2315
2316 let mut room_response = http::response::Room::new();
2318 set_room_joined(&mut room_response, user_b_id);
2319 let response = response_with_room(room_id_2, room_response);
2320 client
2321 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2322 .await
2323 .expect("Failed to process sync");
2324
2325 let room_2 = client.get_room(room_id_2).unwrap();
2326 assert!(room_2.is_direct().await.unwrap());
2327 }
2328
2329 #[async_test]
2330 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2331 let user_id = user_id!("@raclette:patate");
2332 let client = logged_in_base_client(Some(user_id)).await;
2333 let room_id_0 = room_id!("!r0");
2334 let room_id_1 = room_id!("!r1");
2335 let room_id_2 = room_id!("!r2");
2336
2337 let requested_required_states = RequestedRequiredStates::from(&{
2354 let mut request = http::Request::new();
2355
2356 request.room_subscriptions.insert(room_id_0.to_owned(), {
2357 let mut room_subscription = http::request::RoomSubscription::default();
2358
2359 room_subscription
2360 .required_state
2361 .push((StateEventType::RoomEncryption, "".to_owned()));
2362
2363 room_subscription
2364 });
2365
2366 request
2367 });
2368
2369 let mut response = http::Response::new("0".to_owned());
2370
2371 {
2375 let not_encrypted_room = http::response::Room::new();
2376 let mut encrypted_room = http::response::Room::new();
2377 set_room_is_encrypted(&mut encrypted_room, user_id);
2378
2379 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2380 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2381 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2382 }
2383
2384 client
2385 .process_sliding_sync(&response, &requested_required_states)
2386 .await
2387 .expect("Failed to process sync");
2388
2389 assert_matches!(
2391 client.get_room(room_id_0).unwrap().encryption_state(),
2392 EncryptionState::Encrypted
2393 );
2394 assert_matches!(
2395 client.get_room(room_id_1).unwrap().encryption_state(),
2396 EncryptionState::Encrypted
2397 );
2398 assert_matches!(
2400 client.get_room(room_id_2).unwrap().encryption_state(),
2401 EncryptionState::NotEncrypted
2402 )
2403 }
2404
2405 #[async_test]
2406 async fn test_room_encryption_state_is_unknown() {
2407 let user_id = user_id!("@raclette:patate");
2408 let client = logged_in_base_client(Some(user_id)).await;
2409 let room_id_0 = room_id!("!r0");
2410 let room_id_1 = room_id!("!r1");
2411
2412 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
2425
2426 let mut response = http::Response::new("0".to_owned());
2427
2428 {
2430 let not_encrypted_room = http::response::Room::new();
2431 let mut encrypted_room = http::response::Room::new();
2432 set_room_is_encrypted(&mut encrypted_room, user_id);
2433
2434 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
2435 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
2436 }
2437
2438 client
2439 .process_sliding_sync(&response, &requested_required_states)
2440 .await
2441 .expect("Failed to process sync");
2442
2443 assert_matches!(
2446 client.get_room(room_id_0).unwrap().encryption_state(),
2447 EncryptionState::Encrypted
2448 );
2449 assert_matches!(
2452 client.get_room(room_id_1).unwrap().encryption_state(),
2453 EncryptionState::Unknown
2454 );
2455 }
2456
2457 #[cfg(feature = "e2e-encryption")]
2458 async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2459 let room = make_room();
2460 let mut room_info = room.clone_info();
2461 cache_latest_events(&room, &mut room_info, events, None, None).await;
2462 room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2463 room.latest_event().map(|latest_event| latest_event.event().clone())
2464 }
2465
2466 #[cfg(feature = "e2e-encryption")]
2467 fn rawev_id(event: TimelineEvent) -> String {
2468 event.event_id().unwrap().to_string()
2469 }
2470
2471 fn ev_id(event: Option<TimelineEvent>) -> String {
2472 event.unwrap().event_id().unwrap().to_string()
2473 }
2474
2475 #[cfg(feature = "e2e-encryption")]
2476 fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2477 events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2478 }
2479
2480 #[cfg(feature = "e2e-encryption")]
2481 fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2482 events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2483 }
2484
2485 #[cfg(feature = "e2e-encryption")]
2486 fn make_room() -> Room {
2487 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2488
2489 Room::new(
2490 user_id!("@u:e.co"),
2491 Arc::new(MemoryStore::new()),
2492 room_id!("!r:e.co"),
2493 RoomState::Joined,
2494 sender,
2495 )
2496 }
2497
2498 fn make_raw_event(event_type: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2499 Raw::from_json_string(
2500 json!({
2501 "type": event_type,
2502 "event_id": id,
2503 "content": { "msgtype": "m.text", "body": "my msg" },
2504 "sender": "@u:h.uk",
2505 "origin_server_ts": 12344445,
2506 })
2507 .to_string(),
2508 )
2509 .unwrap()
2510 }
2511
2512 #[cfg(feature = "e2e-encryption")]
2513 fn make_event(event_type: &str, id: &str) -> TimelineEvent {
2514 TimelineEvent::new(make_raw_event(event_type, id))
2515 }
2516
2517 #[cfg(feature = "e2e-encryption")]
2518 fn make_encrypted_event(id: &str) -> TimelineEvent {
2519 TimelineEvent::new_utd_event(
2520 Raw::from_json_string(
2521 json!({
2522 "type": "m.room.encrypted",
2523 "event_id": id,
2524 "content": {
2525 "algorithm": "m.megolm.v1.aes-sha2",
2526 "ciphertext": "",
2527 "sender_key": "",
2528 "device_id": "",
2529 "session_id": "",
2530 },
2531 "sender": "@u:h.uk",
2532 "origin_server_ts": 12344445,
2533 })
2534 .to_string(),
2535 )
2536 .unwrap(),
2537 UnableToDecryptInfo {
2538 session_id: Some("".to_owned()),
2539 reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2540 },
2541 )
2542 }
2543
2544 async fn membership(
2545 client: &BaseClient,
2546 room_id: &RoomId,
2547 user_id: &UserId,
2548 ) -> MembershipState {
2549 let room = client.get_room(room_id).expect("Room not found!");
2550 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2551 member.membership().clone()
2552 }
2553
2554 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2555 let room = client.get_room(room_id).expect("Room not found!");
2556 room.direct_targets()
2557 }
2558
2559 async fn create_dm(
2562 client: &BaseClient,
2563 room_id: &RoomId,
2564 my_id: &UserId,
2565 their_id: &UserId,
2566 other_state: MembershipState,
2567 ) {
2568 let mut room = http::response::Room::new();
2569 set_room_joined(&mut room, my_id);
2570
2571 match other_state {
2572 MembershipState::Join => {
2573 room.joined_count = Some(uint!(2));
2574 room.invited_count = None;
2575 }
2576
2577 MembershipState::Invite => {
2578 room.joined_count = Some(uint!(1));
2579 room.invited_count = Some(uint!(1));
2580 }
2581
2582 _ => {
2583 room.joined_count = Some(uint!(1));
2584 room.invited_count = None;
2585 }
2586 }
2587
2588 room.required_state.push(make_membership_event(their_id, other_state));
2589
2590 let mut response = response_with_room(room_id, room);
2591 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2592 client
2593 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2594 .await
2595 .expect("Failed to process sync");
2596 }
2597
2598 async fn update_room_membership(
2600 client: &BaseClient,
2601 room_id: &RoomId,
2602 user_id: &UserId,
2603 new_state: MembershipState,
2604 ) {
2605 let mut room = http::response::Room::new();
2606 room.required_state.push(make_membership_event(user_id, new_state));
2607 let response = response_with_room(room_id, room);
2608 client
2609 .process_sliding_sync(&response, &RequestedRequiredStates::default())
2610 .await
2611 .expect("Failed to process sync");
2612 }
2613
2614 fn set_direct_with(
2615 response: &mut http::Response,
2616 user_id: OwnedUserId,
2617 room_ids: Vec<OwnedRoomId>,
2618 ) {
2619 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2620 BTreeMap::new();
2621 direct_content.insert(user_id.into(), room_ids);
2622 response
2623 .extensions
2624 .account_data
2625 .global
2626 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2627 }
2628
2629 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2630 let mut response = http::Response::new("5".to_owned());
2631 response.rooms.insert(room_id.to_owned(), room);
2632 response
2633 }
2634
2635 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2636 let mut room = http::response::Room::new();
2637
2638 let mut avatar_event_content = RoomAvatarEventContent::new();
2639 avatar_event_content.url = Some(avatar_uri.to_owned());
2640
2641 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2642
2643 room
2644 }
2645
2646 fn room_with_canonical_alias(
2647 room_alias_id: &RoomAliasId,
2648 user_id: &UserId,
2649 ) -> http::response::Room {
2650 let mut room = http::response::Room::new();
2651
2652 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2653 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2654
2655 room.required_state.push(make_state_event(
2656 user_id,
2657 "",
2658 canonical_alias_event_content,
2659 None,
2660 ));
2661
2662 room
2663 }
2664
2665 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
2666 let mut room = http::response::Room::new();
2667
2668 let name_event_content = RoomNameEventContent::new(name.to_owned());
2669
2670 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
2671
2672 room
2673 }
2674
2675 fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2676 let mut room = http::response::Room::new();
2677 room.timeline.extend(
2678 events
2679 .iter()
2680 .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2681 .collect::<Vec<_>>(),
2682 );
2683 room
2684 }
2685
2686 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2687 room.required_state.push(make_state_event(
2688 sender,
2689 "",
2690 RoomNameEventContent::new(name),
2691 None,
2692 ));
2693 }
2694
2695 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2696 let evt = Raw::new(&json!({
2700 "type": "m.room.member",
2701 "sender": inviter,
2702 "content": {
2703 "is_direct": true,
2704 "membership": "invite",
2705 },
2706 "state_key": invitee,
2707 }))
2708 .expect("Failed to make raw event")
2709 .cast();
2710
2711 room.invite_state = Some(vec![evt]);
2712
2713 room.required_state.push(make_state_event(
2716 inviter,
2717 invitee.as_str(),
2718 RoomMemberEventContent::new(MembershipState::Invite),
2719 None,
2720 ));
2721 }
2722
2723 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2724 let evt = Raw::new(&json!({
2728 "type": "m.room.member",
2729 "sender": knocker,
2730 "content": {
2731 "is_direct": true,
2732 "membership": "knock",
2733 },
2734 "state_key": knocker,
2735 }))
2736 .expect("Failed to make raw event")
2737 .cast();
2738
2739 room.invite_state = Some(vec![evt]);
2740 }
2741
2742 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2743 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2744 }
2745
2746 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2747 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2748 }
2749
2750 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2751 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2752 }
2753
2754 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
2755 room.required_state.push(make_encryption_event(user_id));
2756 }
2757
2758 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2759 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2760 }
2761
2762 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
2763 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
2764 }
2765
2766 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2767 Raw::new(&json!({
2768 "type": content.event_type(),
2769 "content": content,
2770 }))
2771 .expect("Failed to create account data event")
2772 .cast()
2773 }
2774
2775 fn make_state_event<C: StateEventContent, E>(
2776 sender: &UserId,
2777 state_key: &str,
2778 content: C,
2779 prev_content: Option<C>,
2780 ) -> Raw<E> {
2781 let unsigned = if let Some(prev_content) = prev_content {
2782 json!({ "prev_content": prev_content })
2783 } else {
2784 json!({})
2785 };
2786
2787 Raw::new(&json!({
2788 "type": content.event_type(),
2789 "state_key": state_key,
2790 "content": content,
2791 "event_id": event_id!("$evt"),
2792 "sender": sender,
2793 "origin_server_ts": 10,
2794 "unsigned": unsigned,
2795 }))
2796 .expect("Failed to create state event")
2797 .cast()
2798 }
2799}