1#[cfg(feature = "e2e-encryption")]
18use matrix_sdk_common::deserialized_responses::ProcessedToDeviceEvent;
19use matrix_sdk_common::timer;
20use ruma::{
21 OwnedRoomId, api::client::sync::sync_events::v5 as http, events::receipt::SyncReceiptEvent,
22 serde::Raw,
23};
24use tokio::sync::MutexGuard;
25use tracing::{instrument, trace};
26
27use super::BaseClient;
28use crate::{
29 RequestedRequiredStates,
30 error::Result,
31 response_processors as processors,
32 store::{AvatarCache, ambiguity_map::AmbiguityCache},
33 sync::{RoomUpdates, SyncResponse},
34};
35
36impl BaseClient {
37 #[cfg(feature = "e2e-encryption")]
45 pub async fn process_sliding_sync_e2ee(
46 &self,
47 to_device: Option<&http::response::ToDevice>,
48 e2ee: &http::response::E2EE,
49 state_store_guard: &MutexGuard<'_, ()>,
50 ) -> Result<Option<Vec<ProcessedToDeviceEvent>>> {
51 if to_device.is_none() && e2ee.is_empty() {
52 return Ok(None);
53 }
54
55 trace!(
56 to_device_events =
57 to_device.map(|to_device| to_device.events.len()).unwrap_or_default(),
58 device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
59 device_unused_fallback_key_types =
60 e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
61 "Processing sliding sync e2ee events",
62 );
63
64 let olm_machine = self.olm_machine().await;
65
66 let context = processors::Context::default();
67
68 let processors::e2ee::to_device::Output { processed_to_device_events } =
69 processors::e2ee::to_device::from_msc4186(
70 to_device,
71 e2ee,
72 olm_machine.as_ref(),
73 &self.decryption_settings,
74 )
75 .await?;
76
77 processors::changes::save_and_apply(
78 context,
79 &self.state_store,
80 state_store_guard,
81 &self.ignore_user_list_changes,
82 None,
83 )
84 .await?;
85
86 Ok(Some(processed_to_device_events))
87 }
88
89 #[instrument(skip_all, level = "trace")]
96 pub async fn process_sliding_sync(
97 &self,
98 response: &http::Response,
99 requested_required_states: &RequestedRequiredStates,
100 state_store_guard: &MutexGuard<'_, ()>,
101 ) -> Result<SyncResponse> {
102 let http::Response { rooms, lists, extensions, .. } = response;
103
104 trace!(
105 rooms = rooms.len(),
106 lists = lists.len(),
107 has_extensions = !extensions.is_empty(),
108 "Processing sliding sync room events"
109 );
110
111 if rooms.is_empty() && extensions.is_empty() {
112 return Ok(SyncResponse::default());
115 }
116
117 let _timer = timer!(tracing::Level::TRACE, "_method");
118
119 let mut context = processors::Context::default();
120
121 let state_store = self.state_store.clone();
122 let mut ambiguity_cache = AmbiguityCache::new(state_store.inner.clone());
123 let mut avatar_cache = AvatarCache::new(state_store.inner.clone());
124
125 let global_account_data_processor =
126 processors::account_data::global(&extensions.account_data.global);
127 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
128
129 let mut room_updates = RoomUpdates::default();
130 let mut notifications = Default::default();
131
132 let user_id = self
133 .session_meta()
134 .expect("Sliding sync shouldn't run without an authenticated user")
135 .user_id
136 .to_owned();
137
138 for (room_id, room_response) in rooms {
139 let Some((room_info, room_update)) = processors::room::msc4186::update_any_room(
140 &mut context,
141 &user_id,
142 processors::room::RoomCreationData::new(
143 room_id,
144 requested_required_states,
145 &mut ambiguity_cache,
146 &mut avatar_cache,
147 ),
148 room_response,
149 &extensions.account_data.rooms,
150 #[cfg(feature = "e2e-encryption")]
151 processors::e2ee::E2EE::new(
152 self.olm_machine().await.as_ref(),
153 &self.decryption_settings,
154 self.handle_verification_events,
155 ),
156 processors::notification::Notification::new(
157 &push_rules,
158 &mut notifications,
159 &self.state_store,
160 ),
161 )
162 .await?
163 else {
164 continue;
165 };
166
167 context.state_changes.add_room(room_info);
168
169 let room_id = room_id.to_owned();
170
171 use processors::room::msc4186::RoomUpdateKind;
172
173 match room_update {
174 RoomUpdateKind::Joined(joined_room_update) => {
175 room_updates.joined.insert(room_id, joined_room_update);
176 }
177 RoomUpdateKind::Left(left_room_update) => {
178 room_updates.left.insert(room_id, left_room_update);
179 }
180 RoomUpdateKind::Invited(invited_room_update) => {
181 room_updates.invited.insert(room_id, invited_room_update);
182 }
183 RoomUpdateKind::Knocked(knocked_room_update) => {
184 room_updates.knocked.insert(room_id, knocked_room_update);
185 }
186 }
187 }
188
189 processors::room::msc4186::extensions::dispatch_typing_ephemeral_events(
194 &extensions.typing,
195 &mut room_updates.joined,
196 );
197
198 processors::room::msc4186::extensions::room_account_data(
200 &mut context,
201 &extensions.account_data,
202 &mut room_updates,
203 &self.state_store,
204 );
205
206 global_account_data_processor.apply(&mut context, &state_store).await;
207
208 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
209
210 context.state_changes.global_profiles = extensions.profiles.users.clone();
213
214 processors::changes::save_and_apply(
216 context,
217 &self.state_store,
218 state_store_guard,
219 &self.ignore_user_list_changes,
220 None,
221 )
222 .await?;
223
224 self.notify_global_profile_updates(
228 extensions.profiles.users.keys().cloned().collect(),
229 state_store_guard,
230 )?;
231
232 #[cfg(feature = "unstable-msc4354")]
235 {
236 #[cfg(feature = "e2e-encryption")]
237 let olm_machine = self.olm_machine().await;
238
239 processors::room::msc4186::extensions::sticky_events(
240 &extensions.sticky_events,
241 rooms,
242 &room_updates.joined,
243 &self.state_store,
244 #[cfg(feature = "e2e-encryption")]
245 &processors::e2ee::E2EE::new(
246 olm_machine.as_ref(),
247 &self.decryption_settings,
248 false,
249 ),
250 )
251 .await;
252 }
253
254 let mut context = processors::Context::default();
255
256 processors::room::display_name::update_for_rooms(
260 &mut context,
261 &room_updates,
262 &self.state_store,
263 )
264 .await;
265
266 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
268
269 Ok(SyncResponse {
270 rooms: room_updates,
271 notifications,
272 presence: Default::default(),
273 account_data: extensions.account_data.global.clone(),
274 to_device: Default::default(),
275 })
276 }
277
278 #[doc(hidden)]
281 pub async fn process_sliding_sync_receipts_extension_for_room(
282 &self,
283 room_id: &OwnedRoomId,
284 response: &http::Response,
285 state_store_guard: &MutexGuard<'_, ()>,
286 ) -> Result<Option<Raw<SyncReceiptEvent>>> {
287 let mut context = processors::Context::default();
288
289 let mut save_context = false;
290
291 let receipt_ephemeral_event = if let Some(receipt_ephemeral_event) =
293 response.extensions.receipts.rooms.get(room_id)
294 {
295 processors::room::msc4186::extensions::dispatch_receipt_ephemeral_event_for_room(
296 &mut context,
297 room_id,
298 receipt_ephemeral_event,
299 );
300 save_context = true;
301 Some(receipt_ephemeral_event.clone())
302 } else {
303 None
304 };
305
306 if save_context {
308 processors::changes::save_only(context, &self.state_store, state_store_guard).await?;
309 }
310
311 Ok(receipt_ephemeral_event)
312 }
313}
314
315#[cfg(all(test, not(target_family = "wasm")))]
316mod tests {
317 use std::collections::{BTreeMap, HashSet};
318
319 use assert_matches::assert_matches;
320 use matrix_sdk_test::async_test;
321 #[cfg(feature = "unstable-msc4354")]
322 use ruma::events::AnySyncTimelineEvent;
323 use ruma::{
324 JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
325 api::client::sync::sync_events::UnreadNotificationsCount,
326 assign, event_id,
327 events::{
328 GlobalAccountDataEventContent, StateEventContent, StateEventType,
329 direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
330 room::{
331 avatar::RoomAvatarEventContent,
332 canonical_alias::RoomCanonicalAliasEventContent,
333 encryption::RoomEncryptionEventContent,
334 member::{MembershipState, RoomMemberEventContent},
335 name::RoomNameEventContent,
336 pinned_events::RoomPinnedEventsEventContent,
337 },
338 },
339 mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id,
340 profile::{ProfileFieldName, UserProfileChanges, UserProfileUpdate},
341 room_alias_id, room_id,
342 serde::Raw,
343 uint, user_id,
344 };
345 use serde_json::json;
346 #[cfg(feature = "unstable-msc4426")]
347 use stream_assert::{assert_pending, assert_ready};
348
349 use super::http;
350 use crate::{
351 BaseClient, DmRoomDefinition, EncryptionState, RequestedRequiredStates,
352 RoomInfoNotableUpdate, RoomState, SessionMeta,
353 client::ThreadingSupport,
354 room::{RoomHero, RoomInfoNotableUpdateReasons},
355 store::{RoomLoadSettings, StoreConfig},
356 test_utils::logged_in_base_client,
357 };
358
359 #[async_test]
360 async fn test_invited_state_without_update_emits_invited_room() {
361 let client = logged_in_base_client(None).await;
362 let room_id = room_id!("!invite:e.uk");
363 let user_id = client.session_meta().unwrap().user_id.to_owned();
364
365 let mut room = http::response::Room::new();
366 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Invite));
367
368 let response = response_with_room(room_id, room);
369
370 let sync_resp = client
371 .process_sliding_sync(
372 &response,
373 &RequestedRequiredStates::default(),
374 &client.state_store_lock().lock().await,
375 )
376 .await
377 .unwrap();
378
379 assert!(sync_resp.rooms.invited.contains_key(room_id));
380 }
381
382 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
383 use ruma::events::AnyStrippedStateEvent;
384
385 fn invite_state_for(
386 user_id: &UserId,
387 membership: MembershipState,
388 ) -> Vec<Raw<AnyStrippedStateEvent>> {
389 let content = RoomMemberEventContent::new(membership);
390
391 let raw: Raw<AnyStrippedStateEvent> = Raw::from_json_string(
392 serde_json::json!({
393 "type": "m.room.member",
394 "state_key": user_id,
395 "content": content,
396 })
397 .to_string(),
398 )
399 .unwrap();
400
401 vec![raw]
402 }
403
404 #[async_test]
405 async fn test_knocked_state_emits_invited_room() {
406 let client = logged_in_base_client(None).await;
407 let room_id = room_id!("!knock:e.uk");
408 let user_id = client.session_meta().unwrap().user_id.to_owned();
409
410 let mut room = http::response::Room::new();
411 room.invite_state = Some(invite_state_for(&user_id, MembershipState::Knock));
412
413 let response = response_with_room(room_id, room);
414
415 let sync_resp = client
416 .process_sliding_sync(
417 &response,
418 &RequestedRequiredStates::default(),
419 &client.state_store_lock().lock().await,
420 )
421 .await
422 .unwrap();
423
424 assert!(sync_resp.rooms.invited.contains_key(room_id));
426 }
427
428 #[async_test]
429 async fn test_notification_count_set() {
430 let client = logged_in_base_client(None).await;
431
432 let mut response = http::Response::new("42".to_owned());
433 let room_id = room_id!("!room:example.org");
434 let count = assign!(UnreadNotificationsCount::default(), {
435 highlight_count: Some(uint!(13)),
436 notification_count: Some(uint!(37)),
437 });
438
439 response.rooms.insert(
440 room_id.to_owned(),
441 assign!(http::response::Room::new(), {
442 unread_notifications: count.clone()
443 }),
444 );
445
446 let sync_response = client
447 .process_sliding_sync(
448 &response,
449 &RequestedRequiredStates::default(),
450 &client.state_store_lock().lock().await,
451 )
452 .await
453 .expect("Failed to process sync");
454
455 let room = sync_response.rooms.joined.get(room_id).unwrap();
457 assert_eq!(room.unread_notifications, count.clone().into());
458
459 let room = client.get_room(room_id).expect("found room");
461 assert_eq!(room.unread_notification_counts(), count.into());
462 }
463
464 #[async_test]
465 async fn test_can_process_empty_sliding_sync_response() {
466 let client = logged_in_base_client(None).await;
467 let empty_response = http::Response::new("5".to_owned());
468 client
469 .process_sliding_sync(
470 &empty_response,
471 &RequestedRequiredStates::default(),
472 &client.state_store_lock().lock().await,
473 )
474 .await
475 .expect("Failed to process sync");
476 }
477
478 #[cfg(feature = "unstable-msc4354")]
481 fn sticky_event(event_id: &str, content: serde_json::Value) -> Raw<AnySyncTimelineEvent> {
482 serde_json::from_value(json!({
483 "type": "m.rtc.member",
484 "sender": "@alice:example.org",
485 "event_id": event_id,
486 "origin_server_ts": 1,
487 "content": content,
488 "msc4354_sticky": { "duration_ms": 600_000 },
489 "unsigned": { "msc4354_sticky_duration_ttl_ms": 600_000 },
490 }))
491 .unwrap()
492 }
493
494 #[cfg(feature = "unstable-msc4354")]
495 #[async_test]
496 async fn test_sticky_events_are_ingested_from_the_timeline() {
497 let client = logged_in_base_client(None).await;
498 let room_id = room_id!("!room:example.org");
499
500 let mut room = http::response::Room::new();
501 room.timeline.push(sticky_event(
502 "$a:example.org",
503 json!({ "msc4354_sticky_key": "slot", "application": "m.call" }),
504 ));
505
506 client
507 .process_sliding_sync(
508 &response_with_room(room_id, room),
509 &RequestedRequiredStates::default(),
510 &client.state_store_lock().lock().await,
511 )
512 .await
513 .expect("Failed to process sync");
514
515 let room = client.get_room(room_id).expect("found room");
516 let live = room.sticky_events().live();
517 assert_eq!(live.len(), 1);
518 assert_eq!(live[0].key.sender, "@alice:example.org");
519 assert_eq!(live[0].key.sticky_key, "slot");
520 assert_eq!(live[0].event_id, "$a:example.org");
521 assert!(live[0].encryption_info().is_none());
522 }
523
524 #[cfg(feature = "unstable-msc4354")]
525 #[async_test]
526 async fn test_sticky_events_are_ingested_from_the_extension() {
527 let client = logged_in_base_client(None).await;
528 let room_id = room_id!("!room:example.org");
529
530 let mut response = response_with_room(room_id, http::response::Room::new());
532
533 let mut sticky_room = http::response::StickyEventsRoom::default();
534 sticky_room.events = vec![
535 sticky_event(
536 "$a:example.org",
537 json!({ "msc4354_sticky_key": "laptop", "application": "m.call" }),
538 ),
539 sticky_event(
540 "$b:example.org",
541 json!({ "msc4354_sticky_key": "phone", "application": "m.call" }),
542 ),
543 ];
544 response.extensions.sticky_events.rooms.insert(room_id.to_owned(), sticky_room);
545
546 let mut unknown_room = http::response::StickyEventsRoom::default();
548 unknown_room.events = vec![sticky_event(
549 "$c:example.org",
550 json!({ "msc4354_sticky_key": "slot", "application": "m.call" }),
551 )];
552 response
553 .extensions
554 .sticky_events
555 .rooms
556 .insert(room_id!("!unknown:example.org").to_owned(), unknown_room);
557
558 client
559 .process_sliding_sync(
560 &response,
561 &RequestedRequiredStates::default(),
562 &client.state_store_lock().lock().await,
563 )
564 .await
565 .expect("Failed to process sync");
566
567 let room = client.get_room(room_id).expect("found room");
568 let mut live = room.sticky_events().live();
569 live.sort_by(|a, b| a.key.sticky_key.cmp(&b.key.sticky_key));
570 assert_eq!(live.len(), 2);
571 assert_eq!(live[0].key.sticky_key, "laptop");
572 assert_eq!(live[1].key.sticky_key, "phone");
573
574 assert!(client.get_room(room_id!("!unknown:example.org")).is_none());
575 }
576
577 #[cfg(feature = "unstable-msc4354")]
578 #[async_test]
579 async fn test_sticky_events_are_forgotten_when_leaving_the_room() {
580 let client = logged_in_base_client(None).await;
581 let room_id = room_id!("!room:example.org");
582 let user_id = client.session_meta().unwrap().user_id.to_owned();
583
584 let mut room = http::response::Room::new();
585 set_room_joined(&mut room, &user_id);
586 room.timeline.push(sticky_event(
587 "$a:example.org",
588 json!({ "msc4354_sticky_key": "slot", "application": "m.call" }),
589 ));
590
591 client
592 .process_sliding_sync(
593 &response_with_room(room_id, room),
594 &RequestedRequiredStates::default(),
595 &client.state_store_lock().lock().await,
596 )
597 .await
598 .expect("Failed to process sync");
599
600 let room = client.get_room(room_id).expect("found room");
601 let mut subscriber = room.sticky_events().subscribe();
602 assert_eq!(room.sticky_events().live().len(), 1);
603
604 let mut room_response = http::response::Room::new();
606 set_room_left(&mut room_response, &user_id);
607
608 client
609 .process_sliding_sync(
610 &response_with_room(room_id, room_response),
611 &RequestedRequiredStates::default(),
612 &client.state_store_lock().lock().await,
613 )
614 .await
615 .expect("Failed to process sync");
616
617 assert_eq!(room.state(), RoomState::Left);
618 assert!(room.sticky_events().live().is_empty());
619
620 let update = subscriber.try_recv().unwrap();
621 assert_matches!(update.removed.as_slice(), [(key, crate::sticky::RemovalReason::RoomLeft)] => {
622 assert_eq!(key.sticky_key, "slot");
623 });
624 }
625
626 #[cfg(all(feature = "unstable-msc4354", feature = "e2e-encryption"))]
627 #[async_test]
628 async fn test_undecryptable_sticky_event_is_kept_aside_not_mapped() {
629 let client = logged_in_base_client(None).await;
630 let room_id = room_id!("!room:example.org");
631
632 let encrypted: Raw<AnySyncTimelineEvent> = serde_json::from_value(json!({
634 "type": "m.room.encrypted",
635 "sender": "@alice:example.org",
636 "event_id": "$enc:example.org",
637 "origin_server_ts": 1,
638 "content": {
639 "algorithm": "m.megolm.v1.aes-sha2",
640 "ciphertext": "AAAA",
641 "sender_key": "senderkey",
642 "session_id": "session",
643 "device_id": "DEV",
644 },
645 "msc4354_sticky": { "duration_ms": 600_000 },
646 "unsigned": { "msc4354_sticky_duration_ttl_ms": 600_000 },
647 }))
648 .unwrap();
649
650 let mut room = http::response::Room::new();
651 room.timeline.push(encrypted.clone());
652 let mut response = response_with_room(room_id, room);
653
654 let mut sticky_room = http::response::StickyEventsRoom::default();
657 sticky_room.events = vec![encrypted];
658 response.extensions.sticky_events.rooms.insert(room_id.to_owned(), sticky_room);
659
660 client
661 .process_sliding_sync(
662 &response,
663 &RequestedRequiredStates::default(),
664 &client.state_store_lock().lock().await,
665 )
666 .await
667 .expect("Failed to process sync");
668
669 let room = client.get_room(room_id).expect("found room");
670 assert!(room.sticky_events().live().is_empty());
671 assert!(room.sticky_events().has_pending());
672 }
673
674 #[async_test]
675 async fn test_profiles_extension_is_persisted_from_sliding_sync() {
676 let client = logged_in_base_client(None).await;
677
678 let alice = user_id!("@alice:e.uk");
679 let bob = user_id!("@bob:e.uk");
680
681 let mut response = http::Response::new("0".to_owned());
684 response.extensions.profiles.users.insert(
685 alice.to_owned(),
686 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
687 );
688 response.extensions.profiles.users.insert(
689 bob.to_owned(),
690 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
691 );
692
693 client
695 .process_sliding_sync(
696 &response,
697 &RequestedRequiredStates::default(),
698 &client.state_store_lock().lock().await,
699 )
700 .await
701 .expect("Failed to process sync");
702
703 let store = client.state_store();
705
706 let alice_profile = store
707 .get_global_profile(alice)
708 .await
709 .expect("Failed to read profile")
710 .expect("Alice's profile should be saved");
711 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
712 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice")));
713
714 let bob_profile = store
715 .get_global_profile(bob)
716 .await
717 .expect("Failed to read profile")
718 .expect("Bob's profile should be saved");
719 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
720 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
721
722 let mut response = http::Response::new("1".to_owned());
724 response.extensions.profiles.users.insert(
725 alice.to_owned(),
726 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
727 );
728
729 client
730 .process_sliding_sync(
731 &response,
732 &RequestedRequiredStates::default(),
733 &client.state_store_lock().lock().await,
734 )
735 .await
736 .expect("Failed to process sync");
737
738 let alice_profile = store
740 .get_global_profile(alice)
741 .await
742 .expect("Failed to read profile")
743 .expect("Alice's profile should be saved");
744 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
745 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice Updated")));
746
747 let bob_profile = store
748 .get_global_profile(bob)
749 .await
750 .expect("Failed to read profile")
751 .expect("Bob's profile should still be saved");
752 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
753 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
754 }
755
756 #[async_test]
757 async fn test_profiles_extension_broadcasts_global_profile_updates() {
758 let client = logged_in_base_client(None).await;
759
760 let alice = user_id!("@alice:e.uk");
761 let bob = user_id!("@bob:e.uk");
762
763 let mut global_profile_updates = client.subscribe_to_global_profile_updates();
765
766 let mut response = http::Response::new("0".to_owned());
769 response.extensions.profiles.users.insert(
770 alice.to_owned(),
771 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
772 );
773 response.extensions.profiles.users.insert(
774 bob.to_owned(),
775 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
776 );
777 client
778 .process_sliding_sync(
779 &response,
780 &RequestedRequiredStates::default(),
781 &client.state_store_lock().lock().await,
782 )
783 .await
784 .expect("Failed to process sync");
785
786 let users =
788 global_profile_updates.recv().await.expect("should receive a global profile update");
789 assert_eq!(users.len(), 2);
790 assert!(users.contains(alice));
791 assert!(users.contains(bob));
792
793 let mut response = http::Response::new("1".to_owned());
795 response.extensions.profiles.users.insert(
796 alice.to_owned(),
797 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
798 );
799 client
800 .process_sliding_sync(
801 &response,
802 &RequestedRequiredStates::default(),
803 &client.state_store_lock().lock().await,
804 )
805 .await
806 .expect("Failed to process sync");
807
808 let users =
810 global_profile_updates.recv().await.expect("should receive a global profile update");
811 assert_eq!(users.len(), 1);
812 assert!(users.contains(alice));
813 }
814
815 #[async_test]
816 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
817 let client = logged_in_base_client(None).await;
819 let room_id = room_id!("!r:e.uk");
820
821 let mut room = http::response::Room::new();
824 room.joined_count = Some(uint!(41));
825 let response = response_with_room(room_id, room);
826 let sync_resp = client
827 .process_sliding_sync(
828 &response,
829 &RequestedRequiredStates::default(),
830 &client.state_store_lock().lock().await,
831 )
832 .await
833 .expect("Failed to process sync");
834
835 let client_room = client.get_room(room_id).expect("No room found");
837 assert_eq!(client_room.room_id(), room_id);
838 assert_eq!(client_room.joined_members_count(), 41);
839 assert_eq!(client_room.state(), RoomState::Joined);
840
841 assert!(sync_resp.rooms.joined.contains_key(room_id));
843 assert!(!sync_resp.rooms.left.contains_key(room_id));
844 assert!(!sync_resp.rooms.invited.contains_key(room_id));
845 }
846
847 #[async_test]
848 async fn test_missing_room_name_event() {
849 let client = logged_in_base_client(None).await;
851 let room_id = room_id!("!r:e.uk");
852
853 let mut room = http::response::Room::new();
856 room.name = Some("little room".to_owned());
857 let response = response_with_room(room_id, room);
858 let sync_resp = client
859 .process_sliding_sync(
860 &response,
861 &RequestedRequiredStates::default(),
862 &client.state_store_lock().lock().await,
863 )
864 .await
865 .expect("Failed to process sync");
866
867 let client_room = client.get_room(room_id).expect("No room found");
870 assert!(client_room.name().is_none());
871 assert_eq!(
872 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
873 "Empty Room"
874 );
875 assert_eq!(client_room.state(), RoomState::Joined);
876
877 assert!(sync_resp.rooms.joined.contains_key(room_id));
879 assert!(!sync_resp.rooms.left.contains_key(room_id));
880 assert!(!sync_resp.rooms.invited.contains_key(room_id));
881 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
882 }
883
884 #[async_test]
885 async fn test_room_name_event() {
886 let client = logged_in_base_client(None).await;
888 let room_id = room_id!("!r:e.uk");
889
890 let mut room = http::response::Room::new();
893
894 room.name = Some("little room".to_owned());
895 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
896
897 let response = response_with_room(room_id, room);
898 client
899 .process_sliding_sync(
900 &response,
901 &RequestedRequiredStates::default(),
902 &client.state_store_lock().lock().await,
903 )
904 .await
905 .expect("Failed to process sync");
906
907 let client_room = client.get_room(room_id).expect("No room found");
909 assert_eq!(client_room.name().as_deref(), Some("The Name"));
910 assert_eq!(
911 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
912 "The Name"
913 );
914 }
915
916 #[async_test]
917 async fn test_missing_invited_room_name_event() {
918 let client = logged_in_base_client(None).await;
920 let room_id = room_id!("!r:e.uk");
921 let user_id = user_id!("@w:e.uk");
922 let inviter = user_id!("@john:mastodon.org");
923
924 let mut room = http::response::Room::new();
927 set_room_invited(&mut room, inviter, user_id);
928 room.name = Some("name from sliding sync response".to_owned());
929 let response = response_with_room(room_id, room);
930 let sync_resp = client
931 .process_sliding_sync(
932 &response,
933 &RequestedRequiredStates::default(),
934 &client.state_store_lock().lock().await,
935 )
936 .await
937 .expect("Failed to process sync");
938
939 let client_room = client.get_room(room_id).expect("No room found");
941 assert!(client_room.name().is_none());
942
943 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
945
946 assert_eq!(client_room.state(), RoomState::Invited);
947
948 assert!(!sync_resp.rooms.joined.contains_key(room_id));
950 assert!(!sync_resp.rooms.left.contains_key(room_id));
951 assert!(sync_resp.rooms.invited.contains_key(room_id));
952 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
953 }
954
955 #[async_test]
956 async fn test_invited_room_name_event() {
957 let client = logged_in_base_client(None).await;
959 let room_id = room_id!("!r:e.uk");
960 let user_id = user_id!("@w:e.uk");
961 let inviter = user_id!("@john:mastodon.org");
962
963 let mut room = http::response::Room::new();
966
967 set_room_invited(&mut room, inviter, user_id);
968
969 room.name = Some("name from sliding sync response".to_owned());
970 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
971
972 let response = response_with_room(room_id, room);
973 client
974 .process_sliding_sync(
975 &response,
976 &RequestedRequiredStates::default(),
977 &client.state_store_lock().lock().await,
978 )
979 .await
980 .expect("Failed to process sync");
981
982 let client_room = client.get_room(room_id).expect("No room found");
984 assert_eq!(client_room.name().as_deref(), Some("The Name"));
985 assert_eq!(
986 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
987 "The Name"
988 );
989 }
990
991 #[async_test]
992 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
993 let client = logged_in_base_client(None).await;
995 let room_id = room_id!("!r:e.uk");
996 let user_id = client.session_meta().unwrap().user_id.to_owned();
997
998 let mut room = http::response::Room::new();
1001 set_room_knocked(&mut room, &user_id);
1002
1003 let response = response_with_room(room_id, room);
1004 client
1005 .process_sliding_sync(
1006 &response,
1007 &RequestedRequiredStates::default(),
1008 &client.state_store_lock().lock().await,
1009 )
1010 .await
1011 .expect("Failed to process sync");
1012
1013 let client_room = client.get_room(room_id).expect("No room found");
1015 assert_eq!(client_room.state(), RoomState::Knocked);
1016 }
1017
1018 #[async_test]
1019 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
1020 {
1021 let client = logged_in_base_client(None).await;
1023 let room_id = room_id!("!r:e.uk");
1024 let user_id = user_id!("@w:e.uk");
1025
1026 let mut room = http::response::Room::new();
1028 set_room_knocked(&mut room, user_id);
1029
1030 let response = response_with_room(room_id, room);
1031 client
1032 .process_sliding_sync(
1033 &response,
1034 &RequestedRequiredStates::default(),
1035 &client.state_store_lock().lock().await,
1036 )
1037 .await
1038 .expect("Failed to process sync");
1039
1040 let client_room = client.get_room(room_id).expect("No room found");
1043 assert_eq!(client_room.state(), RoomState::Invited);
1044 }
1045
1046 #[async_test]
1047 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
1048 {
1049 let client = logged_in_base_client(None).await;
1051 let room_id = room_id!("!r:e.uk");
1052 let user_id = client.session_meta().unwrap().user_id.to_owned();
1053
1054 let mut room = http::response::Room::new();
1056 let event = Raw::new(&json!({
1057 "type": "m.room.member",
1058 "sender": user_id,
1059 "content": {
1060 "is_direct": true,
1061 "membership": "join",
1062 },
1063 "state_key": user_id,
1064 }))
1065 .expect("Failed to make raw event")
1066 .cast_unchecked();
1067 room.invite_state = Some(vec![event]);
1068
1069 let response = response_with_room(room_id, room);
1070 client
1071 .process_sliding_sync(
1072 &response,
1073 &RequestedRequiredStates::default(),
1074 &client.state_store_lock().lock().await,
1075 )
1076 .await
1077 .expect("Failed to process sync");
1078
1079 let client_room = client.get_room(room_id).expect("No room found");
1081 assert_eq!(client_room.state(), RoomState::Invited);
1082 }
1083
1084 #[async_test]
1085 async fn test_left_a_room_from_required_state_event() {
1086 let client = logged_in_base_client(None).await;
1088 let room_id = room_id!("!r:e.uk");
1089 let user_id = user_id!("@u:e.uk");
1090
1091 let mut room = http::response::Room::new();
1093 set_room_joined(&mut room, user_id);
1094 let response = response_with_room(room_id, room);
1095 client
1096 .process_sliding_sync(
1097 &response,
1098 &RequestedRequiredStates::default(),
1099 &client.state_store_lock().lock().await,
1100 )
1101 .await
1102 .expect("Failed to process sync");
1103 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1104
1105 let mut room = http::response::Room::new();
1107 set_room_left(&mut room, user_id);
1108 let response = response_with_room(room_id, room);
1109 let sync_resp = client
1110 .process_sliding_sync(
1111 &response,
1112 &RequestedRequiredStates::default(),
1113 &client.state_store_lock().lock().await,
1114 )
1115 .await
1116 .expect("Failed to process sync");
1117
1118 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1120
1121 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1123 assert!(sync_resp.rooms.left.contains_key(room_id));
1124 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1125 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1126 }
1127
1128 #[async_test]
1129 async fn test_kick_or_ban_updates_room_to_left() {
1130 for membership in [MembershipState::Leave, MembershipState::Ban] {
1131 let room_id = room_id!("!r:e.uk");
1132 let user_a_id = user_id!("@a:e.uk");
1133 let user_b_id = user_id!("@b:e.uk");
1134 let client = logged_in_base_client(Some(user_a_id)).await;
1135
1136 let mut room = http::response::Room::new();
1138 set_room_joined(&mut room, user_a_id);
1139 let response = response_with_room(room_id, room);
1140 client
1141 .process_sliding_sync(
1142 &response,
1143 &RequestedRequiredStates::default(),
1144 &client.state_store_lock().lock().await,
1145 )
1146 .await
1147 .expect("Failed to process sync");
1148 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1149
1150 let mut room = http::response::Room::new();
1152 room.required_state.push(make_state_event(
1153 user_b_id,
1154 user_a_id.as_str(),
1155 RoomMemberEventContent::new(membership.clone()),
1156 None,
1157 ));
1158 let response = response_with_room(room_id, room);
1159 let sync_resp = client
1160 .process_sliding_sync(
1161 &response,
1162 &RequestedRequiredStates::default(),
1163 &client.state_store_lock().lock().await,
1164 )
1165 .await
1166 .expect("Failed to process sync");
1167
1168 match membership {
1169 MembershipState::Leave => {
1170 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1172 }
1173 MembershipState::Ban => {
1174 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1176 }
1177 _ => panic!("Unexpected membership state found: {membership}"),
1178 }
1179
1180 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1182 assert!(sync_resp.rooms.left.contains_key(room_id));
1183 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1184 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1185 }
1186 }
1187
1188 #[async_test]
1189 async fn test_left_a_room_from_timeline_state_event() {
1190 let client = logged_in_base_client(None).await;
1192 let room_id = room_id!("!r:e.uk");
1193 let user_id = user_id!("@u:e.uk");
1194
1195 let mut room = http::response::Room::new();
1197 set_room_joined(&mut room, user_id);
1198 let response = response_with_room(room_id, room);
1199 client
1200 .process_sliding_sync(
1201 &response,
1202 &RequestedRequiredStates::default(),
1203 &client.state_store_lock().lock().await,
1204 )
1205 .await
1206 .expect("Failed to process sync");
1207 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1208
1209 let mut room = http::response::Room::new();
1211 set_room_left_as_timeline_event(&mut room, user_id);
1212 let response = response_with_room(room_id, room);
1213 client
1214 .process_sliding_sync(
1215 &response,
1216 &RequestedRequiredStates::default(),
1217 &client.state_store_lock().lock().await,
1218 )
1219 .await
1220 .expect("Failed to process sync");
1221
1222 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1225 }
1226
1227 #[async_test]
1228 async fn test_can_be_reinvited_to_a_left_room() {
1229 let client = logged_in_base_client(None).await;
1233 let room_id = room_id!("!r:e.uk");
1234 let user_id = user_id!("@u:e.uk");
1235
1236 let mut room = http::response::Room::new();
1238 set_room_joined(&mut room, user_id);
1239 let response = response_with_room(room_id, room);
1240 client
1241 .process_sliding_sync(
1242 &response,
1243 &RequestedRequiredStates::default(),
1244 &client.state_store_lock().lock().await,
1245 )
1246 .await
1247 .expect("Failed to process sync");
1248 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1250
1251 let mut room = http::response::Room::new();
1253 set_room_left(&mut room, user_id);
1254 let response = response_with_room(room_id, room);
1255 client
1256 .process_sliding_sync(
1257 &response,
1258 &RequestedRequiredStates::default(),
1259 &client.state_store_lock().lock().await,
1260 )
1261 .await
1262 .expect("Failed to process sync");
1263 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1265
1266 let mut room = http::response::Room::new();
1268 set_room_invited(&mut room, user_id, user_id);
1269 let response = response_with_room(room_id, room);
1270 client
1271 .process_sliding_sync(
1272 &response,
1273 &RequestedRequiredStates::default(),
1274 &client.state_store_lock().lock().await,
1275 )
1276 .await
1277 .expect("Failed to process sync");
1278
1279 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1281 }
1282
1283 #[async_test]
1284 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1285 let room_id = room_id!("!r:e.uk");
1286 let user_a_id = user_id!("@a:e.uk");
1287 let user_b_id = user_id!("@b:e.uk");
1288
1289 let client = logged_in_base_client(None).await;
1291 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1292
1293 assert!(
1295 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1296 );
1297 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1298
1299 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1301
1302 assert!(
1307 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1308 );
1309 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1310 }
1311
1312 #[async_test]
1313 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
1314 {
1315 let room_id = room_id!("!r:e.uk");
1316 let user_a_id = user_id!("@a:e.uk");
1317 let user_b_id = user_id!("@b:e.uk");
1318
1319 let client = logged_in_base_client(None).await;
1321 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1322
1323 assert!(
1325 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1326 );
1327 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1328
1329 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1331
1332 assert!(
1337 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1338 );
1339 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1340 }
1341
1342 #[async_test]
1343 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1344 let room_id = room_id!("!r:bar.org");
1345 let user_a_id = user_id!("@a:bar.org");
1346 let user_b_id = user_id!("@b:bar.org");
1347
1348 let client = logged_in_base_client(None).await;
1350 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1351
1352 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1354
1355 assert!(
1357 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1358 );
1359 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1360
1361 let room = client.get_room(room_id).unwrap();
1362
1363 assert_eq!(room.active_members_count(), 2);
1364 assert_eq!(room.joined_members_count(), 2);
1365 assert_eq!(room.invited_members_count(), 0);
1366 }
1367
1368 #[async_test]
1369 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1370 let room_id = room_id!("!r:bar.org");
1371 let user_a_id = user_id!("@a:bar.org");
1372 let user_b_id = user_id!("@b:bar.org");
1373
1374 let client = logged_in_base_client(None).await;
1376 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1377
1378 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1380
1381 assert!(
1383 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1384 );
1385 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1386
1387 let room = client.get_room(room_id).unwrap();
1388
1389 assert_eq!(room.active_members_count(), 2);
1390 assert_eq!(room.joined_members_count(), 1);
1391 assert_eq!(room.invited_members_count(), 1);
1392 }
1393
1394 #[async_test]
1395 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1396 let client = logged_in_base_client(None).await;
1398 let room_id = room_id!("!r:e.uk");
1399
1400 let room = {
1402 let mut room = http::response::Room::new();
1403 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1404
1405 room
1406 };
1407 let response = response_with_room(room_id, room);
1408 client
1409 .process_sliding_sync(
1410 &response,
1411 &RequestedRequiredStates::default(),
1412 &client.state_store_lock().lock().await,
1413 )
1414 .await
1415 .expect("Failed to process sync");
1416
1417 let client_room = client.get_room(room_id).expect("No room found");
1419 assert_eq!(
1420 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1421 "med1"
1422 );
1423 }
1424
1425 #[async_test]
1426 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1427 let client = logged_in_base_client(None).await;
1429 let room_id = room_id!("!r:e.uk");
1430
1431 let room = {
1435 let mut room = http::response::Room::new();
1436 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1437
1438 room
1439 };
1440 let response = response_with_room(room_id, room);
1441 client
1442 .process_sliding_sync(
1443 &response,
1444 &RequestedRequiredStates::default(),
1445 &client.state_store_lock().lock().await,
1446 )
1447 .await
1448 .expect("Failed to process sync");
1449
1450 let client_room = client.get_room(room_id).expect("No room found");
1452 assert_eq!(
1453 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1454 "med1"
1455 );
1456
1457 let room = http::response::Room::new();
1461 let response = response_with_room(room_id, room);
1462 client
1463 .process_sliding_sync(
1464 &response,
1465 &RequestedRequiredStates::default(),
1466 &client.state_store_lock().lock().await,
1467 )
1468 .await
1469 .expect("Failed to process sync");
1470
1471 let client_room = client.get_room(room_id).expect("No room found");
1473 assert_eq!(
1474 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1475 "med1"
1476 );
1477
1478 let room = {
1483 let mut room = http::response::Room::new();
1484 room.avatar = JsOption::Null;
1485
1486 room
1487 };
1488 let response = response_with_room(room_id, room);
1489 client
1490 .process_sliding_sync(
1491 &response,
1492 &RequestedRequiredStates::default(),
1493 &client.state_store_lock().lock().await,
1494 )
1495 .await
1496 .expect("Failed to process sync");
1497
1498 let client_room = client.get_room(room_id).expect("No room found");
1500 assert!(client_room.avatar_url().is_none());
1501 }
1502
1503 #[async_test]
1504 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1505 let client = logged_in_base_client(None).await;
1507 let room_id = room_id!("!r:e.uk");
1508 let user_id = user_id!("@u:e.uk");
1509
1510 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1512 let response = response_with_room(room_id, room);
1513 client
1514 .process_sliding_sync(
1515 &response,
1516 &RequestedRequiredStates::default(),
1517 &client.state_store_lock().lock().await,
1518 )
1519 .await
1520 .expect("Failed to process sync");
1521
1522 let client_room = client.get_room(room_id).expect("No room found");
1524 assert_eq!(
1525 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1526 "med1"
1527 );
1528 }
1529
1530 #[async_test]
1531 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1532 let client = logged_in_base_client(None).await;
1534 let room_id = room_id!("!r:e.uk");
1535 let user_id = user_id!("@u:e.uk");
1536
1537 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1538
1539 let mut room = http::response::Room::new();
1541 set_room_invited(&mut room, user_id, user_id);
1542 let response = response_with_room(room_id, room);
1543 let sync_resp = client
1544 .process_sliding_sync(
1545 &response,
1546 &RequestedRequiredStates::default(),
1547 &client.state_store_lock().lock().await,
1548 )
1549 .await
1550 .expect("Failed to process sync");
1551
1552 let client_room = client.get_room(room_id).expect("No room found");
1554 assert_eq!(client_room.room_id(), room_id);
1555 assert_eq!(client_room.state(), RoomState::Invited);
1556
1557 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1559 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1560
1561 assert_matches!(
1562 room_info_notable_update.recv().await,
1563 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1564 assert_eq!(received_room_id, room_id);
1565 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1567 }
1568 );
1569 assert_matches!(
1570 room_info_notable_update.recv().await,
1571 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1572 assert_eq!(received_room_id, room_id);
1573 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1575 }
1576 );
1577 assert!(room_info_notable_update.is_empty());
1578 }
1579
1580 #[async_test]
1581 async fn test_knock_room_is_added_to_client_and_knock_list() {
1582 let client = logged_in_base_client(None).await;
1584 let room_id = room_id!("!r:e.uk");
1585 let user_id = user_id!("@u:e.uk");
1586
1587 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1588
1589 let mut room = http::response::Room::new();
1591 set_room_knocked(&mut room, user_id);
1592 let response = response_with_room(room_id, room);
1593 let sync_resp = client
1594 .process_sliding_sync(
1595 &response,
1596 &RequestedRequiredStates::default(),
1597 &client.state_store_lock().lock().await,
1598 )
1599 .await
1600 .expect("Failed to process sync");
1601
1602 let client_room = client.get_room(room_id).expect("No room found");
1604 assert_eq!(client_room.room_id(), room_id);
1605 assert_eq!(client_room.state(), RoomState::Knocked);
1606
1607 assert!(!sync_resp.rooms.knocked[room_id].knock_state.is_empty());
1609 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1610
1611 assert_matches!(
1612 room_info_notable_update.recv().await,
1613 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1614 assert_eq!(received_room_id, room_id);
1615 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1617 }
1618 );
1619 assert_matches!(
1620 room_info_notable_update.recv().await,
1621 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1622 assert_eq!(received_room_id, room_id);
1623 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1625 }
1626 );
1627 assert!(room_info_notable_update.is_empty());
1628 }
1629
1630 #[async_test]
1631 async fn test_invited_room_stays_invited_when_a_later_response_has_no_invite_state() {
1632 let client = logged_in_base_client(None).await;
1634 let room_id = room_id!("!r:e.uk");
1635 let user_id = user_id!("@u:e.uk");
1636
1637 let mut room = http::response::Room::new();
1638 set_room_invited(&mut room, user_id, user_id);
1639 let response = response_with_room(room_id, room);
1640 client
1641 .process_sliding_sync(
1642 &response,
1643 &RequestedRequiredStates::default(),
1644 &client.state_store_lock().lock().await,
1645 )
1646 .await
1647 .expect("Failed to process sync");
1648
1649 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1651
1652 let response = response_with_room(room_id, http::response::Room::new());
1655 let sync_resp = client
1656 .process_sliding_sync(
1657 &response,
1658 &RequestedRequiredStates::default(),
1659 &client.state_store_lock().lock().await,
1660 )
1661 .await
1662 .expect("Failed to process sync");
1663
1664 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1666 assert!(sync_resp.rooms.invited.contains_key(room_id));
1667 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1668 }
1669
1670 #[async_test]
1671 async fn test_invited_room_becomes_joined_from_required_state_event() {
1672 let client = logged_in_base_client(None).await;
1674 let room_id = room_id!("!r:e.uk");
1675 let user_id = user_id!("@u:e.uk");
1676
1677 let mut room = http::response::Room::new();
1678 set_room_invited(&mut room, user_id, user_id);
1679 let response = response_with_room(room_id, room);
1680 client
1681 .process_sliding_sync(
1682 &response,
1683 &RequestedRequiredStates::default(),
1684 &client.state_store_lock().lock().await,
1685 )
1686 .await
1687 .expect("Failed to process sync");
1688
1689 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1691
1692 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1693
1694 let mut room = http::response::Room::new();
1697 set_room_joined(&mut room, user_id);
1698 let response = response_with_room(room_id, room);
1699 let sync_resp = client
1700 .process_sliding_sync(
1701 &response,
1702 &RequestedRequiredStates::default(),
1703 &client.state_store_lock().lock().await,
1704 )
1705 .await
1706 .expect("Failed to process sync");
1707
1708 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1710
1711 assert!(sync_resp.rooms.joined.contains_key(room_id));
1712 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1713
1714 assert_matches!(
1716 room_info_notable_update.recv().await,
1717 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1718 assert_eq!(received_room_id, room_id);
1719 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1720 }
1721 );
1722 }
1723
1724 #[async_test]
1725 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1726 let client = logged_in_base_client(None).await;
1728 let room_id = room_id!("!r:e.uk");
1729 let user_id = user_id!("@u:e.uk");
1730
1731 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1734 set_room_invited(&mut room, user_id, user_id);
1735 let response = response_with_room(room_id, room);
1736 client
1737 .process_sliding_sync(
1738 &response,
1739 &RequestedRequiredStates::default(),
1740 &client.state_store_lock().lock().await,
1741 )
1742 .await
1743 .expect("Failed to process sync");
1744
1745 let client_room = client.get_room(room_id).expect("No room found");
1747 assert_eq!(
1748 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1749 "med1"
1750 );
1751 }
1752
1753 #[async_test]
1754 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1755 {
1756 let client = logged_in_base_client(None).await;
1758 let room_id = room_id!("!r:e.uk");
1759 let user_id = user_id!("@u:e.uk");
1760 let room_alias_id = room_alias_id!("#myroom:e.uk");
1761
1762 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1765 set_room_invited(&mut room, user_id, user_id);
1766 let response = response_with_room(room_id, room);
1767 client
1768 .process_sliding_sync(
1769 &response,
1770 &RequestedRequiredStates::default(),
1771 &client.state_store_lock().lock().await,
1772 )
1773 .await
1774 .expect("Failed to process sync");
1775
1776 let client_room = client.get_room(room_id).expect("No room found");
1778 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1779 }
1780
1781 #[async_test]
1782 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1783 let client = logged_in_base_client(None).await;
1785 let room_id = room_id!("!r:e.uk");
1786 let user_id = user_id!("@u:e.uk");
1787 let room_alias_id = room_alias_id!("#myroom:e.uk");
1788
1789 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1792 room.name = Some("This came from the server".to_owned());
1793 let response = response_with_room(room_id, room);
1794 client
1795 .process_sliding_sync(
1796 &response,
1797 &RequestedRequiredStates::default(),
1798 &client.state_store_lock().lock().await,
1799 )
1800 .await
1801 .expect("Failed to process sync");
1802
1803 let client_room = client.get_room(room_id).expect("No room found");
1806 assert_eq!(
1807 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1808 "myroom"
1809 );
1810 assert!(client_room.name().is_none());
1811 }
1812
1813 #[async_test]
1814 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1815 let client = logged_in_base_client(None).await;
1816 let user_id = user_id!("@u:e.uk");
1817 let room_id = room_id!("!r:e.uk");
1818
1819 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1820
1821 let room = room_with_name("Hello World", user_id);
1822 let response = response_with_room(room_id, room);
1823 client
1824 .process_sliding_sync(
1825 &response,
1826 &RequestedRequiredStates::default(),
1827 &client.state_store_lock().lock().await,
1828 )
1829 .await
1830 .expect("Failed to process sync");
1831
1832 let room = client.get_room(room_id).expect("No room found");
1833 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1834
1835 assert_matches!(
1836 room_info_notable_update.recv().await,
1837 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1838 assert_eq!(received_room_id, room_id);
1839 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1840 }
1841 );
1842 assert_matches!(
1843 room_info_notable_update.recv().await,
1844 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1845 assert_eq!(received_room_id, room_id);
1846 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1848 }
1849 );
1850 assert!(room_info_notable_update.is_empty());
1851 }
1852
1853 #[async_test]
1854 async fn test_display_name_is_persisted_from_sliding_sync() {
1855 let user_id = user_id!("@u:e.uk");
1856 let room_id = room_id!("!r:e.uk");
1857 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1858 let state_store;
1859
1860 {
1861 let client = {
1862 let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1863 state_store = store.state_store.clone();
1864
1865 let client =
1866 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1867 client
1868 .activate(
1869 session_meta.clone(),
1870 RoomLoadSettings::default(),
1871 #[cfg(feature = "e2e-encryption")]
1872 None,
1873 )
1874 .await
1875 .expect("`activate` failed!");
1876
1877 client
1878 };
1879
1880 let room = room_with_name("Hello World", user_id);
1883 let response = response_with_room(room_id, room);
1884 client
1885 .process_sliding_sync(
1886 &response,
1887 &RequestedRequiredStates::default(),
1888 &client.state_store_lock().lock().await,
1889 )
1890 .await
1891 .expect("Failed to process sync");
1892
1893 let room = client.get_room(room_id).expect("No room found");
1894 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1895 }
1896
1897 {
1898 let client = {
1899 let mut store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1900 store.state_store = state_store;
1901 let client =
1902 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1903 client
1904 .activate(
1905 session_meta,
1906 RoomLoadSettings::default(),
1907 #[cfg(feature = "e2e-encryption")]
1908 None,
1909 )
1910 .await
1911 .expect("`activate` failed!");
1912
1913 client
1914 };
1915
1916 let room = client.get_room(room_id).expect("No room found");
1917 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1918 }
1919 }
1920
1921 #[async_test]
1922 async fn test_compute_heroes_from_sliding_sync() {
1923 let client = logged_in_base_client(None).await;
1925 let room_id = room_id!("!r:e.uk");
1926 let gordon = owned_user_id!("@gordon:e.uk");
1927 let alice = owned_user_id!("@alice:e.uk");
1928
1929 let mut room = http::response::Room::new();
1932 room.heroes = Some(vec![
1933 assign!(http::response::Hero::new(gordon), {
1934 name: Some("Gordon".to_owned()),
1935 }),
1936 assign!(http::response::Hero::new(alice), {
1937 name: Some("Alice".to_owned()),
1938 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1939 }),
1940 ]);
1941 let response = response_with_room(room_id, room);
1942 let _sync_resp = client
1943 .process_sliding_sync(
1944 &response,
1945 &RequestedRequiredStates::default(),
1946 &client.state_store_lock().lock().await,
1947 )
1948 .await
1949 .expect("Failed to process sync");
1950
1951 let client_room = client.get_room(room_id).expect("No room found");
1953 assert_eq!(client_room.room_id(), room_id);
1954 assert_eq!(client_room.state(), RoomState::Joined);
1955
1956 assert_eq!(
1958 client_room.clone_info().summary.heroes(),
1959 &[
1960 RoomHero {
1961 user_id: owned_user_id!("@gordon:e.uk"),
1962 display_name: Some("Gordon".to_owned()),
1963 avatar_url: None
1964 },
1965 RoomHero {
1966 user_id: owned_user_id!("@alice:e.uk"),
1967 display_name: Some("Alice".to_owned()),
1968 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1969 },
1970 ]
1971 );
1972 }
1973
1974 #[cfg(feature = "unstable-msc4426")]
1975 #[async_test]
1976 async fn test_hero_global_profile_update_triggers_notable_update() {
1977 let client = logged_in_base_client(None).await;
1978 let room_id = room_id!("!r:e.uk");
1979 let alice = owned_user_id!("@alice:e.uk");
1980
1981 let mut room = http::response::Room::new();
1983 room.heroes = Some(vec![assign!(http::response::Hero::new(alice.clone()), {
1984 name: Some("Alice".to_owned()),
1985 })]);
1986 let response = response_with_room(room_id, room);
1987 client
1988 .process_sliding_sync(
1989 &response,
1990 &RequestedRequiredStates::default(),
1991 &client.state_store_lock().lock().await,
1992 )
1993 .await
1994 .expect("Failed to process sync");
1995
1996 let room = client.get_room(room_id).expect("The room should be known");
1997 let mut room_info_subscriber = room.subscribe_info();
1998 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1999
2000 assert_pending!(room_info_subscriber);
2001
2002 let mut response = http::Response::new("1".to_owned());
2005 response.extensions.profiles.users.insert(
2006 alice.clone(),
2007 make_profile_update(ProfileFieldName::Status, json!({ "text": "Away", "emoji": "🌴" })),
2008 );
2009 client
2010 .process_sliding_sync(
2011 &response,
2012 &RequestedRequiredStates::default(),
2013 &client.state_store_lock().lock().await,
2014 )
2015 .await
2016 .expect("Failed to process sync");
2017
2018 assert_matches!(
2021 room_info_notable_update.recv().await,
2022 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
2023 assert_eq!(received_room_id, room_id);
2024 assert!(reasons.contains(RoomInfoNotableUpdateReasons::HEROES));
2025 }
2026 );
2027 assert!(room_info_notable_update.is_empty());
2028
2029 assert_ready!(room_info_subscriber);
2032 assert_pending!(room_info_subscriber);
2033
2034 let heroes = room.heroes().await;
2036 assert_eq!(heroes.len(), 1);
2037 assert_eq!(heroes[0].user_id, alice);
2038 let status = heroes[0].status.as_ref().expect("Alice's status should be set");
2039 assert_eq!(status.text, "Away");
2040 assert_eq!(status.emoji, "🌴");
2041 }
2042
2043 #[async_test]
2044 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2045 let client = logged_in_base_client(None).await;
2047 let room_id = room_id!("!r:e.uk");
2048
2049 let room = assign!(http::response::Room::new(), {
2052 bump_stamp: Some(42u32.into()),
2053 });
2054 let response = response_with_room(room_id, room);
2055 client
2056 .process_sliding_sync(
2057 &response,
2058 &RequestedRequiredStates::default(),
2059 &client.state_store_lock().lock().await,
2060 )
2061 .await
2062 .expect("Failed to process sync");
2063
2064 let client_room = client.get_room(room_id).expect("No room found");
2066 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2067 }
2068
2069 #[async_test]
2070 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2071 let client = logged_in_base_client(None).await;
2073 let room_id = room_id!("!r:e.uk");
2074
2075 {
2076 let room = assign!(http::response::Room::new(), {
2079 bump_stamp: Some(42u32.into()),
2080 });
2081 let response = response_with_room(room_id, room);
2082 client
2083 .process_sliding_sync(
2084 &response,
2085 &RequestedRequiredStates::default(),
2086 &client.state_store_lock().lock().await,
2087 )
2088 .await
2089 .expect("Failed to process sync");
2090
2091 let client_room = client.get_room(room_id).expect("No room found");
2093 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2094 }
2095
2096 {
2097 let room = assign!(http::response::Room::new(), {
2100 bump_stamp: None,
2101 });
2102 let response = response_with_room(room_id, room);
2103 client
2104 .process_sliding_sync(
2105 &response,
2106 &RequestedRequiredStates::default(),
2107 &client.state_store_lock().lock().await,
2108 )
2109 .await
2110 .expect("Failed to process sync");
2111
2112 let client_room = client.get_room(room_id).expect("No room found");
2114 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2115 }
2116
2117 {
2118 let room = assign!(http::response::Room::new(), {
2121 bump_stamp: Some(153u32.into()),
2122 });
2123 let response = response_with_room(room_id, room);
2124 client
2125 .process_sliding_sync(
2126 &response,
2127 &RequestedRequiredStates::default(),
2128 &client.state_store_lock().lock().await,
2129 )
2130 .await
2131 .expect("Failed to process sync");
2132
2133 let client_room = client.get_room(room_id).expect("No room found");
2135 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
2136 }
2137 }
2138
2139 #[async_test]
2140 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2141 let client = logged_in_base_client(None).await;
2143 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2144 let room_id = room_id!("!r:e.uk");
2145
2146 let room = assign!(http::response::Room::new(), {
2149 bump_stamp: Some(42u32.into()),
2150 });
2151 let response = response_with_room(room_id, room);
2152 client
2153 .process_sliding_sync(
2154 &response,
2155 &RequestedRequiredStates::default(),
2156 &client.state_store_lock().lock().await,
2157 )
2158 .await
2159 .expect("Failed to process sync");
2160
2161 assert_matches!(
2164 room_info_notable_update_stream.recv().await,
2165 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2166 assert_eq!(received_room_id, room_id);
2167 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2168 }
2169 );
2170 assert_matches!(
2171 room_info_notable_update_stream.recv().await,
2172 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2173 assert_eq!(received_room_id, room_id);
2174 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
2175 }
2176 );
2177 assert!(room_info_notable_update_stream.is_empty());
2178
2179 let room = assign!(http::response::Room::new(), {
2182 bump_stamp: Some(43u32.into()),
2183 });
2184 let response = response_with_room(room_id, room);
2185 client
2186 .process_sliding_sync(
2187 &response,
2188 &RequestedRequiredStates::default(),
2189 &client.state_store_lock().lock().await,
2190 )
2191 .await
2192 .expect("Failed to process sync");
2193
2194 assert_matches!(
2196 room_info_notable_update_stream.recv().await,
2197 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2198 assert_eq!(received_room_id, room_id);
2199 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2200 }
2201 );
2202 assert!(room_info_notable_update_stream.is_empty());
2203 }
2204
2205 #[async_test]
2206 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2207 let client = logged_in_base_client(None).await;
2209 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2210
2211 let room_id = room_id!("!r:e.uk");
2213 let room = http::response::Room::new();
2214 let response = response_with_room(room_id, room);
2215 client
2216 .process_sliding_sync(
2217 &response,
2218 &RequestedRequiredStates::default(),
2219 &client.state_store_lock().lock().await,
2220 )
2221 .await
2222 .expect("Failed to process sync");
2223
2224 assert_matches!(
2226 room_info_notable_update_stream.recv().await,
2227 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2228 assert_eq!(received_room_id, room_id);
2229 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
2230 }
2231 );
2232 assert_matches!(
2233 room_info_notable_update_stream.recv().await,
2234 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2235 assert_eq!(received_room_id, room_id);
2236 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
2237 }
2238 );
2239
2240 let room_id = room_id!("!r:e.uk");
2243 let events = vec![
2244 Raw::from_json_string(
2245 json!({
2246 "type": "m.room.member",
2247 "event_id": "$3",
2248 "content": { "membership": "join" },
2249 "sender": "@u:h.uk",
2250 "origin_server_ts": 12344445,
2251 "state_key": "@u:e.uk",
2252 })
2253 .to_string(),
2254 )
2255 .unwrap(),
2256 ];
2257 let room = assign!(http::response::Room::new(), {
2258 required_state: events,
2259 });
2260 let response = response_with_room(room_id, room);
2261 client
2262 .process_sliding_sync(
2263 &response,
2264 &RequestedRequiredStates::default(),
2265 &client.state_store_lock().lock().await,
2266 )
2267 .await
2268 .expect("Failed to process sync");
2269
2270 assert_matches!(
2273 room_info_notable_update_stream.recv().await,
2274 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2275 assert_eq!(received_room_id, room_id);
2276 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
2277 }
2278 );
2279 assert!(room_info_notable_update_stream.is_empty());
2280
2281 let events = vec![
2282 Raw::from_json_string(
2283 json!({
2284 "type": "m.room.member",
2285 "event_id": "$3",
2286 "content": { "membership": "leave" },
2287 "sender": "@u:h.uk",
2288 "origin_server_ts": 12344445,
2289 "state_key": "@u:e.uk",
2290 })
2291 .to_string(),
2292 )
2293 .unwrap(),
2294 ];
2295 let room = assign!(http::response::Room::new(), {
2296 required_state: events,
2297 });
2298 let response = response_with_room(room_id, room);
2299 client
2300 .process_sliding_sync(
2301 &response,
2302 &RequestedRequiredStates::default(),
2303 &client.state_store_lock().lock().await,
2304 )
2305 .await
2306 .expect("Failed to process sync");
2307
2308 assert_matches!(
2310 room_info_notable_update_stream.recv().await,
2311 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2312 assert_eq!(received_room_id, room_id);
2313 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2314 }
2315 );
2316 assert!(room_info_notable_update_stream.is_empty());
2317 }
2318
2319 #[async_test]
2320 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2321 let client = logged_in_base_client(None).await;
2323 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2324
2325 let room_id = room_id!("!r:e.uk");
2327 let room = http::response::Room::new();
2328 let response = response_with_room(room_id, room);
2329 client
2330 .process_sliding_sync(
2331 &response,
2332 &RequestedRequiredStates::default(),
2333 &client.state_store_lock().lock().await,
2334 )
2335 .await
2336 .expect("Failed to process sync");
2337
2338 assert_matches!(
2341 room_info_notable_update_stream.recv().await,
2342 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2343 assert_eq!(received_room_id, room_id);
2344 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2345 }
2346 );
2347 assert_matches!(
2348 room_info_notable_update_stream.recv().await,
2349 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2350 assert_eq!(received_room_id, room_id);
2351 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2352 }
2353 );
2354 assert!(room_info_notable_update_stream.is_empty());
2355
2356 let room_id = room_id!("!r:e.uk");
2359 let room_account_data_events = vec![
2360 Raw::from_json_string(
2361 json!({
2362 "type": "m.marked_unread",
2363 "event_id": "$1",
2364 "content": { "unread": true },
2365 "sender": client.session_meta().unwrap().user_id,
2366 "origin_server_ts": 12344445,
2367 })
2368 .to_string(),
2369 )
2370 .unwrap(),
2371 ];
2372 let mut response = response_with_room(room_id, http::response::Room::new());
2373 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2374
2375 client
2376 .process_sliding_sync(
2377 &response,
2378 &RequestedRequiredStates::default(),
2379 &client.state_store_lock().lock().await,
2380 )
2381 .await
2382 .expect("Failed to process sync");
2383
2384 assert_matches!(
2386 room_info_notable_update_stream.recv().await,
2387 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2388 assert_eq!(received_room_id, room_id);
2389 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2390 }
2391 );
2392
2393 client
2395 .process_sliding_sync(
2396 &response,
2397 &RequestedRequiredStates::default(),
2398 &client.state_store_lock().lock().await,
2399 )
2400 .await
2401 .expect("Failed to process sync");
2402
2403 assert_matches!(
2404 room_info_notable_update_stream.recv().await,
2405 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2406 assert_eq!(received_room_id, room_id);
2407 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2408 }
2409 );
2410 assert!(room_info_notable_update_stream.is_empty());
2411
2412 let room_account_data_events = vec![
2414 Raw::from_json_string(
2415 json!({
2416 "type": "m.marked_unread",
2417 "event_id": "$1",
2418 "content": { "unread": false },
2419 "sender": client.session_meta().unwrap().user_id,
2420 "origin_server_ts": 12344445,
2421 })
2422 .to_string(),
2423 )
2424 .unwrap(),
2425 ];
2426 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2427 client
2428 .process_sliding_sync(
2429 &response,
2430 &RequestedRequiredStates::default(),
2431 &client.state_store_lock().lock().await,
2432 )
2433 .await
2434 .expect("Failed to process sync");
2435
2436 assert_matches!(
2437 room_info_notable_update_stream.recv().await,
2438 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2439 assert_eq!(received_room_id, room_id);
2440 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2441 }
2442 );
2443 assert!(room_info_notable_update_stream.is_empty());
2444 }
2445
2446 #[async_test]
2447 async fn test_empty_room_account_data_does_not_create_a_room_update() {
2448 let client = logged_in_base_client(None).await;
2449
2450 let room_id_a = room_id!("!a:e.uk");
2451 let room_id_b = room_id!("!b:e.uk");
2452 let user_id = client.session_meta().unwrap().user_id.clone();
2453
2454 let mut response = http::Response::new("0".to_owned());
2455 for room_id in [room_id_a, room_id_b] {
2456 let mut room = http::response::Room::new();
2457 set_room_joined(&mut room, &user_id);
2458 response.rooms.insert(room_id.to_owned(), room);
2459 }
2460 client
2461 .process_sliding_sync(
2462 &response,
2463 &RequestedRequiredStates::default(),
2464 &client.state_store_lock().lock().await,
2465 )
2466 .await
2467 .expect("Failed to process sync");
2468
2469 let mut response = response_with_room(room_id_a, http::response::Room::new());
2470 response.extensions.account_data.rooms.insert(room_id_b.to_owned(), vec![]);
2471
2472 let sync_response = client
2473 .process_sliding_sync(
2474 &response,
2475 &RequestedRequiredStates::default(),
2476 &client.state_store_lock().lock().await,
2477 )
2478 .await
2479 .expect("Failed to process sync");
2480
2481 assert!(sync_response.rooms.joined.contains_key(room_id_a));
2482 assert!(!sync_response.rooms.joined.contains_key(room_id_b));
2483 assert!(sync_response.rooms.left.is_empty());
2484 }
2485
2486 #[async_test]
2487 async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
2488 let client = logged_in_base_client(None).await;
2490 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2491
2492 let room_id = room_id!("!r:e.uk");
2494 let room = http::response::Room::new();
2495 let response = response_with_room(room_id, room);
2496 client
2497 .process_sliding_sync(
2498 &response,
2499 &RequestedRequiredStates::default(),
2500 &client.state_store_lock().lock().await,
2501 )
2502 .await
2503 .expect("Failed to process sync");
2504
2505 assert_matches!(
2508 room_info_notable_update_stream.recv().await,
2509 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2510 assert_eq!(received_room_id, room_id);
2511 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2512 }
2513 );
2514 assert_matches!(
2515 room_info_notable_update_stream.recv().await,
2516 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2517 assert_eq!(received_room_id, room_id);
2518 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2519 }
2520 );
2521 assert!(room_info_notable_update_stream.is_empty());
2522
2523 let room_account_data_events = vec![
2526 Raw::from_json_string(
2527 json!({
2528 "type": "m.fully_read",
2529 "content": { "event_id": "$first" },
2530 })
2531 .to_string(),
2532 )
2533 .unwrap(),
2534 ];
2535 let mut response = response_with_room(room_id, http::response::Room::new());
2536 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2537
2538 client
2539 .process_sliding_sync(
2540 &response,
2541 &RequestedRequiredStates::default(),
2542 &client.state_store_lock().lock().await,
2543 )
2544 .await
2545 .expect("Failed to process sync");
2546
2547 assert_matches!(
2549 room_info_notable_update_stream.recv().await,
2550 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2551 assert_eq!(received_room_id, room_id);
2552 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2553 }
2554 );
2555
2556 let room = client.get_room(room_id).expect("room should exist");
2557 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2558
2559 client
2561 .process_sliding_sync(
2562 &response,
2563 &RequestedRequiredStates::default(),
2564 &client.state_store_lock().lock().await,
2565 )
2566 .await
2567 .expect("Failed to process sync");
2568
2569 assert_matches!(
2570 room_info_notable_update_stream.recv().await,
2571 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2572 assert_eq!(received_room_id, room_id);
2573 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2574 }
2575 );
2576 assert!(room_info_notable_update_stream.is_empty());
2577
2578 let room_account_data_events = vec![
2580 Raw::from_json_string(
2581 json!({
2582 "type": "m.fully_read",
2583 "content": { "event_id": "$second" },
2584 })
2585 .to_string(),
2586 )
2587 .unwrap(),
2588 ];
2589 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2590 client
2591 .process_sliding_sync(
2592 &response,
2593 &RequestedRequiredStates::default(),
2594 &client.state_store_lock().lock().await,
2595 )
2596 .await
2597 .expect("Failed to process sync");
2598
2599 assert_matches!(
2600 room_info_notable_update_stream.recv().await,
2601 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2602 assert_eq!(received_room_id, room_id);
2603 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2604 }
2605 );
2606 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2607 assert!(room_info_notable_update_stream.is_empty());
2608 }
2609
2610 #[async_test]
2611 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2612 let client = logged_in_base_client(None).await;
2614 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2615
2616 let room_id = room_id!("!r:e.uk");
2618 let room = http::response::Room::new();
2619 let response = response_with_room(room_id, room);
2620 client
2621 .process_sliding_sync(
2622 &response,
2623 &RequestedRequiredStates::default(),
2624 &client.state_store_lock().lock().await,
2625 )
2626 .await
2627 .expect("Failed to process sync");
2628
2629 assert_matches!(
2632 room_info_notable_update_stream.recv().await,
2633 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2634 assert_eq!(received_room_id, room_id);
2635 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2636 }
2637 );
2638 assert_matches!(
2639 room_info_notable_update_stream.recv().await,
2640 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2641 assert_eq!(received_room_id, room_id);
2642 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2643 }
2644 );
2645 assert!(room_info_notable_update_stream.is_empty());
2646
2647 let room_id = room_id!("!r:e.uk");
2650 let unstable_room_account_data_events = vec![
2651 Raw::from_json_string(
2652 json!({
2653 "type": "com.famedly.marked_unread",
2654 "event_id": "$1",
2655 "content": { "unread": true },
2656 "sender": client.session_meta().unwrap().user_id,
2657 "origin_server_ts": 12344445,
2658 })
2659 .to_string(),
2660 )
2661 .unwrap(),
2662 ];
2663 let mut response = response_with_room(room_id, http::response::Room::new());
2664 response
2665 .extensions
2666 .account_data
2667 .rooms
2668 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2669
2670 client
2671 .process_sliding_sync(
2672 &response,
2673 &RequestedRequiredStates::default(),
2674 &client.state_store_lock().lock().await,
2675 )
2676 .await
2677 .expect("Failed to process sync");
2678
2679 assert_matches!(
2681 room_info_notable_update_stream.recv().await,
2682 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2683 assert_eq!(received_room_id, room_id);
2684 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2685 }
2686 );
2687 assert!(room_info_notable_update_stream.is_empty());
2688
2689 let stable_room_account_data_events = vec![
2692 Raw::from_json_string(
2693 json!({
2694 "type": "m.marked_unread",
2695 "event_id": "$1",
2696 "content": { "unread": false },
2697 "sender": client.session_meta().unwrap().user_id,
2698 "origin_server_ts": 12344445,
2699 })
2700 .to_string(),
2701 )
2702 .unwrap(),
2703 ];
2704 response
2705 .extensions
2706 .account_data
2707 .rooms
2708 .insert(room_id.to_owned(), stable_room_account_data_events);
2709 client
2710 .process_sliding_sync(
2711 &response,
2712 &RequestedRequiredStates::default(),
2713 &client.state_store_lock().lock().await,
2714 )
2715 .await
2716 .expect("Failed to process sync");
2717
2718 assert_matches!(
2720 room_info_notable_update_stream.recv().await,
2721 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2722 assert_eq!(received_room_id, room_id);
2723 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2724 }
2725 );
2726 assert!(room_info_notable_update_stream.is_empty());
2727
2728 response
2731 .extensions
2732 .account_data
2733 .rooms
2734 .insert(room_id.to_owned(), unstable_room_account_data_events);
2735 client
2736 .process_sliding_sync(
2737 &response,
2738 &RequestedRequiredStates::default(),
2739 &client.state_store_lock().lock().await,
2740 )
2741 .await
2742 .expect("Failed to process sync");
2743
2744 assert_matches!(
2746 room_info_notable_update_stream.recv().await,
2747 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2748 assert_eq!(received_room_id, room_id);
2749 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2750 }
2751 );
2752 assert!(room_info_notable_update_stream.is_empty());
2753
2754 let stable_room_account_data_events = vec![
2757 Raw::from_json_string(
2758 json!({
2759 "type": "m.marked_unread",
2760 "event_id": "$3",
2761 "content": { "unread": true },
2762 "sender": client.session_meta().unwrap().user_id,
2763 "origin_server_ts": 12344445,
2764 })
2765 .to_string(),
2766 )
2767 .unwrap(),
2768 ];
2769 response
2770 .extensions
2771 .account_data
2772 .rooms
2773 .insert(room_id.to_owned(), stable_room_account_data_events);
2774 client
2775 .process_sliding_sync(
2776 &response,
2777 &RequestedRequiredStates::default(),
2778 &client.state_store_lock().lock().await,
2779 )
2780 .await
2781 .expect("Failed to process sync");
2782
2783 assert_matches!(
2785 room_info_notable_update_stream.recv().await,
2786 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2787 assert_eq!(received_room_id, room_id);
2788 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2789 }
2790 );
2791 assert!(room_info_notable_update_stream.is_empty());
2792 }
2793
2794 #[async_test]
2795 async fn test_pinned_events_are_updated_on_sync() {
2796 let user_a_id = user_id!("@a:e.uk");
2797 let client = logged_in_base_client(Some(user_a_id)).await;
2798 let room_id = room_id!("!r:e.uk");
2799 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2800
2801 let mut room_response = http::response::Room::new();
2803 set_room_joined(&mut room_response, user_a_id);
2804 let response = response_with_room(room_id, room_response);
2805 client
2806 .process_sliding_sync(
2807 &response,
2808 &RequestedRequiredStates::default(),
2809 &client.state_store_lock().lock().await,
2810 )
2811 .await
2812 .expect("Failed to process sync");
2813
2814 let room = client.get_room(room_id).unwrap();
2816 let pinned_event_ids = room.pinned_event_ids();
2817 assert_matches!(pinned_event_ids, None);
2818
2819 let mut room_response = http::response::Room::new();
2821 room_response.required_state.push(make_state_event(
2822 user_a_id,
2823 "",
2824 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2825 None,
2826 ));
2827 let response = response_with_room(room_id, room_response);
2828 client
2829 .process_sliding_sync(
2830 &response,
2831 &RequestedRequiredStates::default(),
2832 &client.state_store_lock().lock().await,
2833 )
2834 .await
2835 .expect("Failed to process sync");
2836
2837 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2838 assert_eq!(pinned_event_ids.len(), 1);
2839 assert_eq!(pinned_event_ids[0], pinned_event_id);
2840
2841 let mut room_response = http::response::Room::new();
2843 room_response.required_state.push(make_state_event(
2844 user_a_id,
2845 "",
2846 RoomPinnedEventsEventContent::new(Vec::new()),
2847 None,
2848 ));
2849 let response = response_with_room(room_id, room_response);
2850 client
2851 .process_sliding_sync(
2852 &response,
2853 &RequestedRequiredStates::default(),
2854 &client.state_store_lock().lock().await,
2855 )
2856 .await
2857 .expect("Failed to process sync");
2858 let pinned_event_ids = room.pinned_event_ids().unwrap();
2859 assert!(pinned_event_ids.is_empty());
2860 }
2861
2862 #[async_test]
2863 async fn test_dms_are_processed_in_any_sync_response() {
2864 let current_user_id = user_id!("@current:e.uk");
2865 let client = logged_in_base_client(Some(current_user_id)).await;
2866 let user_a_id = user_id!("@a:e.uk");
2867 let user_b_id = user_id!("@b:e.uk");
2868 let room_id_1 = room_id!("!r:e.uk");
2869 let room_id_2 = room_id!("!s:e.uk");
2870
2871 let mut room_response = http::response::Room::new();
2872 set_room_joined(&mut room_response, user_a_id);
2873 let mut response = response_with_room(room_id_1, room_response);
2874 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2875 BTreeMap::new();
2876 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2877 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2878 response
2879 .extensions
2880 .account_data
2881 .global
2882 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2883 client
2884 .process_sliding_sync(
2885 &response,
2886 &RequestedRequiredStates::default(),
2887 &client.state_store_lock().lock().await,
2888 )
2889 .await
2890 .expect("Failed to process sync");
2891
2892 let room_1 = client.get_room(room_id_1).unwrap();
2893 assert!(room_1.is_direct().await.unwrap());
2894
2895 let mut room_response = http::response::Room::new();
2897 set_room_joined(&mut room_response, user_b_id);
2898 let response = response_with_room(room_id_2, room_response);
2899 client
2900 .process_sliding_sync(
2901 &response,
2902 &RequestedRequiredStates::default(),
2903 &client.state_store_lock().lock().await,
2904 )
2905 .await
2906 .expect("Failed to process sync");
2907
2908 let room_2 = client.get_room(room_id_2).unwrap();
2909 assert!(room_2.is_direct().await.unwrap());
2910 }
2911
2912 #[async_test]
2913 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2914 let user_id = user_id!("@raclette:patate");
2915 let client = logged_in_base_client(Some(user_id)).await;
2916 let room_id_0 = room_id!("!r0");
2917 let room_id_1 = room_id!("!r1");
2918 let room_id_2 = room_id!("!r2");
2919
2920 let requested_required_states = RequestedRequiredStates::from(&{
2937 let mut request = http::Request::new();
2938
2939 request.room_subscriptions.insert(room_id_0.to_owned(), {
2940 let mut room_subscription = http::request::RoomSubscription::default();
2941
2942 room_subscription
2943 .required_state
2944 .push((StateEventType::RoomEncryption, "".to_owned()));
2945
2946 room_subscription
2947 });
2948
2949 request
2950 });
2951
2952 let mut response = http::Response::new("0".to_owned());
2953
2954 {
2959 let not_encrypted_room = http::response::Room::new();
2960 let mut encrypted_room = http::response::Room::new();
2961 set_room_is_encrypted(&mut encrypted_room, user_id);
2962
2963 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2964 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2965 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2966 }
2967
2968 client
2969 .process_sliding_sync(
2970 &response,
2971 &requested_required_states,
2972 &client.state_store_lock().lock().await,
2973 )
2974 .await
2975 .expect("Failed to process sync");
2976
2977 assert_matches!(
2979 client.get_room(room_id_0).unwrap().encryption_state(),
2980 EncryptionState::Encrypted
2981 );
2982 assert_matches!(
2983 client.get_room(room_id_1).unwrap().encryption_state(),
2984 EncryptionState::Encrypted
2985 );
2986 assert_matches!(
2988 client.get_room(room_id_2).unwrap().encryption_state(),
2989 EncryptionState::NotEncrypted
2990 )
2991 }
2992
2993 #[async_test]
2994 async fn test_room_encryption_state_is_unknown() {
2995 let user_id = user_id!("@raclette:patate");
2996 let client = logged_in_base_client(Some(user_id)).await;
2997 let room_id_0 = room_id!("!r0");
2998 let room_id_1 = room_id!("!r1");
2999
3000 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
3013
3014 let mut response = http::Response::new("0".to_owned());
3015
3016 {
3018 let not_encrypted_room = http::response::Room::new();
3019 let mut encrypted_room = http::response::Room::new();
3020 set_room_is_encrypted(&mut encrypted_room, user_id);
3021
3022 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
3023 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
3024 }
3025
3026 client
3027 .process_sliding_sync(
3028 &response,
3029 &requested_required_states,
3030 &client.state_store_lock().lock().await,
3031 )
3032 .await
3033 .expect("Failed to process sync");
3034
3035 assert_matches!(
3038 client.get_room(room_id_0).unwrap().encryption_state(),
3039 EncryptionState::Encrypted
3040 );
3041 assert_matches!(
3044 client.get_room(room_id_1).unwrap().encryption_state(),
3045 EncryptionState::Unknown
3046 );
3047 }
3048
3049 async fn membership(
3050 client: &BaseClient,
3051 room_id: &RoomId,
3052 user_id: &UserId,
3053 ) -> MembershipState {
3054 let room = client.get_room(room_id).expect("Room not found!");
3055 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
3056 member.membership().clone()
3057 }
3058
3059 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
3060 let room = client.get_room(room_id).expect("Room not found!");
3061 room.direct_targets()
3062 }
3063
3064 async fn create_dm(
3067 client: &BaseClient,
3068 room_id: &RoomId,
3069 my_id: &UserId,
3070 their_id: &UserId,
3071 other_state: MembershipState,
3072 ) {
3073 let mut room = http::response::Room::new();
3074 set_room_joined(&mut room, my_id);
3075
3076 match other_state {
3077 MembershipState::Join => {
3078 room.joined_count = Some(uint!(2));
3079 room.invited_count = None;
3080 }
3081
3082 MembershipState::Invite => {
3083 room.joined_count = Some(uint!(1));
3084 room.invited_count = Some(uint!(1));
3085 }
3086
3087 _ => {
3088 room.joined_count = Some(uint!(1));
3089 room.invited_count = None;
3090 }
3091 }
3092
3093 room.required_state.push(make_membership_event(their_id, other_state));
3094
3095 let mut response = response_with_room(room_id, room);
3096 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
3097 client
3098 .process_sliding_sync(
3099 &response,
3100 &RequestedRequiredStates::default(),
3101 &client.state_store_lock().lock().await,
3102 )
3103 .await
3104 .expect("Failed to process sync");
3105 }
3106
3107 async fn update_room_membership(
3109 client: &BaseClient,
3110 room_id: &RoomId,
3111 user_id: &UserId,
3112 new_state: MembershipState,
3113 ) {
3114 let mut room = http::response::Room::new();
3115 room.required_state.push(make_membership_event(user_id, new_state));
3116 let response = response_with_room(room_id, room);
3117 client
3118 .process_sliding_sync(
3119 &response,
3120 &RequestedRequiredStates::default(),
3121 &client.state_store_lock().lock().await,
3122 )
3123 .await
3124 .expect("Failed to process sync");
3125 }
3126
3127 fn set_direct_with(
3128 response: &mut http::Response,
3129 user_id: OwnedUserId,
3130 room_ids: Vec<OwnedRoomId>,
3131 ) {
3132 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
3133 BTreeMap::new();
3134 direct_content.insert(user_id.into(), room_ids);
3135 response
3136 .extensions
3137 .account_data
3138 .global
3139 .push(make_global_account_data_event(DirectEventContent(direct_content)));
3140 }
3141
3142 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
3143 let mut response = http::Response::new("5".to_owned());
3144 response.rooms.insert(room_id.to_owned(), room);
3145 response
3146 }
3147
3148 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
3149 let mut room = http::response::Room::new();
3150
3151 let mut avatar_event_content = RoomAvatarEventContent::new();
3152 avatar_event_content.url = Some(avatar_uri.to_owned());
3153
3154 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
3155
3156 room
3157 }
3158
3159 fn room_with_canonical_alias(
3160 room_alias_id: &RoomAliasId,
3161 user_id: &UserId,
3162 ) -> http::response::Room {
3163 let mut room = http::response::Room::new();
3164
3165 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
3166 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
3167
3168 room.required_state.push(make_state_event(
3169 user_id,
3170 "",
3171 canonical_alias_event_content,
3172 None,
3173 ));
3174
3175 room
3176 }
3177
3178 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
3179 let mut room = http::response::Room::new();
3180
3181 let name_event_content = RoomNameEventContent::new(name.to_owned());
3182
3183 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
3184
3185 room
3186 }
3187
3188 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
3189 room.required_state.push(make_state_event(
3190 sender,
3191 "",
3192 RoomNameEventContent::new(name),
3193 None,
3194 ));
3195 }
3196
3197 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
3198 let evt = Raw::new(&json!({
3202 "type": "m.room.member",
3203 "sender": inviter,
3204 "content": {
3205 "is_direct": true,
3206 "membership": "invite",
3207 },
3208 "state_key": invitee,
3209 }))
3210 .expect("Failed to make raw event")
3211 .cast_unchecked();
3212
3213 room.invite_state = Some(vec![evt]);
3214
3215 room.required_state.push(make_state_event(
3218 inviter,
3219 invitee.as_str(),
3220 RoomMemberEventContent::new(MembershipState::Invite),
3221 None,
3222 ));
3223 }
3224
3225 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
3226 let evt = Raw::new(&json!({
3230 "type": "m.room.member",
3231 "sender": knocker,
3232 "content": {
3233 "is_direct": true,
3234 "membership": "knock",
3235 },
3236 "state_key": knocker,
3237 }))
3238 .expect("Failed to make raw event")
3239 .cast_unchecked();
3240
3241 room.invite_state = Some(vec![evt]);
3242 }
3243
3244 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
3245 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
3246 }
3247
3248 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
3249 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
3250 }
3251
3252 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
3253 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
3254 }
3255
3256 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
3257 room.required_state.push(make_encryption_event(user_id));
3258 }
3259
3260 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
3261 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
3262 }
3263
3264 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
3265 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
3266 }
3267
3268 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
3269 Raw::new(&json!({
3270 "type": content.event_type(),
3271 "content": content,
3272 }))
3273 .expect("Failed to create account data event")
3274 .cast_unchecked()
3275 }
3276
3277 fn make_state_event<C: StateEventContent, E>(
3278 sender: &UserId,
3279 state_key: &str,
3280 content: C,
3281 prev_content: Option<C>,
3282 ) -> Raw<E> {
3283 let unsigned = if let Some(prev_content) = prev_content {
3284 json!({ "prev_content": prev_content })
3285 } else {
3286 json!({})
3287 };
3288
3289 Raw::new(&json!({
3290 "type": content.event_type(),
3291 "state_key": state_key,
3292 "content": content,
3293 "event_id": event_id!("$evt"),
3294 "sender": sender,
3295 "origin_server_ts": 10,
3296 "unsigned": unsigned,
3297 }))
3298 .expect("Failed to create state event")
3299 .cast_unchecked()
3300 }
3301
3302 fn make_profile_update(field: ProfileFieldName, value: serde_json::Value) -> UserProfileUpdate {
3303 let mut changes = UserProfileChanges::new();
3304 changes.updated.insert(field, value);
3305 UserProfileUpdate::Updated(changes)
3306 }
3307}