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();
658 sticky_room.events = vec![encrypted];
659 response.extensions.sticky_events.rooms.insert(room_id.to_owned(), sticky_room);
660
661 client
662 .process_sliding_sync(
663 &response,
664 &RequestedRequiredStates::default(),
665 &client.state_store_lock().lock().await,
666 )
667 .await
668 .expect("Failed to process sync");
669
670 let room = client.get_room(room_id).expect("found room");
671 assert!(room.sticky_events().live().is_empty());
672 assert!(room.sticky_events().has_pending());
673 }
674
675 #[async_test]
676 async fn test_profiles_extension_is_persisted_from_sliding_sync() {
677 let client = logged_in_base_client(None).await;
678
679 let alice = user_id!("@alice:e.uk");
680 let bob = user_id!("@bob:e.uk");
681
682 let mut response = http::Response::new("0".to_owned());
685 response.extensions.profiles.users.insert(
686 alice.to_owned(),
687 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
688 );
689 response.extensions.profiles.users.insert(
690 bob.to_owned(),
691 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
692 );
693
694 client
696 .process_sliding_sync(
697 &response,
698 &RequestedRequiredStates::default(),
699 &client.state_store_lock().lock().await,
700 )
701 .await
702 .expect("Failed to process sync");
703
704 let store = client.state_store();
706
707 let alice_profile = store
708 .get_global_profile(alice)
709 .await
710 .expect("Failed to read profile")
711 .expect("Alice's profile should be saved");
712 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
713 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice")));
714
715 let bob_profile = store
716 .get_global_profile(bob)
717 .await
718 .expect("Failed to read profile")
719 .expect("Bob's profile should be saved");
720 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
721 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
722
723 let mut response = http::Response::new("1".to_owned());
725 response.extensions.profiles.users.insert(
726 alice.to_owned(),
727 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
728 );
729
730 client
731 .process_sliding_sync(
732 &response,
733 &RequestedRequiredStates::default(),
734 &client.state_store_lock().lock().await,
735 )
736 .await
737 .expect("Failed to process sync");
738
739 let alice_profile = store
741 .get_global_profile(alice)
742 .await
743 .expect("Failed to read profile")
744 .expect("Alice's profile should be saved");
745 let alice_map: BTreeMap<String, serde_json::Value> = alice_profile.into_iter().collect();
746 assert_eq!(alice_map.get("displayname"), Some(&json!("Alice Updated")));
747
748 let bob_profile = store
749 .get_global_profile(bob)
750 .await
751 .expect("Failed to read profile")
752 .expect("Bob's profile should still be saved");
753 let bob_map: BTreeMap<String, serde_json::Value> = bob_profile.into_iter().collect();
754 assert_eq!(bob_map.get("displayname"), Some(&json!("Bob")));
755 }
756
757 #[async_test]
758 async fn test_profiles_extension_broadcasts_global_profile_updates() {
759 let client = logged_in_base_client(None).await;
760
761 let alice = user_id!("@alice:e.uk");
762 let bob = user_id!("@bob:e.uk");
763
764 let mut global_profile_updates = client.subscribe_to_global_profile_updates();
766
767 let mut response = http::Response::new("0".to_owned());
770 response.extensions.profiles.users.insert(
771 alice.to_owned(),
772 make_profile_update(ProfileFieldName::DisplayName, json!("Alice")),
773 );
774 response.extensions.profiles.users.insert(
775 bob.to_owned(),
776 make_profile_update(ProfileFieldName::DisplayName, json!("Bob")),
777 );
778 client
779 .process_sliding_sync(
780 &response,
781 &RequestedRequiredStates::default(),
782 &client.state_store_lock().lock().await,
783 )
784 .await
785 .expect("Failed to process sync");
786
787 let users =
789 global_profile_updates.recv().await.expect("should receive a global profile update");
790 assert_eq!(users.len(), 2);
791 assert!(users.contains(alice));
792 assert!(users.contains(bob));
793
794 let mut response = http::Response::new("1".to_owned());
796 response.extensions.profiles.users.insert(
797 alice.to_owned(),
798 make_profile_update(ProfileFieldName::DisplayName, json!("Alice Updated")),
799 );
800 client
801 .process_sliding_sync(
802 &response,
803 &RequestedRequiredStates::default(),
804 &client.state_store_lock().lock().await,
805 )
806 .await
807 .expect("Failed to process sync");
808
809 let users =
811 global_profile_updates.recv().await.expect("should receive a global profile update");
812 assert_eq!(users.len(), 1);
813 assert!(users.contains(alice));
814 }
815
816 #[async_test]
817 async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
818 let client = logged_in_base_client(None).await;
820 let room_id = room_id!("!r:e.uk");
821
822 let mut room = http::response::Room::new();
825 room.joined_count = Some(uint!(41));
826 let response = response_with_room(room_id, room);
827 let sync_resp = client
828 .process_sliding_sync(
829 &response,
830 &RequestedRequiredStates::default(),
831 &client.state_store_lock().lock().await,
832 )
833 .await
834 .expect("Failed to process sync");
835
836 let client_room = client.get_room(room_id).expect("No room found");
838 assert_eq!(client_room.room_id(), room_id);
839 assert_eq!(client_room.joined_members_count(), 41);
840 assert_eq!(client_room.state(), RoomState::Joined);
841
842 assert!(sync_resp.rooms.joined.contains_key(room_id));
844 assert!(!sync_resp.rooms.left.contains_key(room_id));
845 assert!(!sync_resp.rooms.invited.contains_key(room_id));
846 }
847
848 #[async_test]
849 async fn test_missing_room_name_event() {
850 let client = logged_in_base_client(None).await;
852 let room_id = room_id!("!r:e.uk");
853
854 let mut room = http::response::Room::new();
857 room.name = Some("little room".to_owned());
858 let response = response_with_room(room_id, room);
859 let sync_resp = client
860 .process_sliding_sync(
861 &response,
862 &RequestedRequiredStates::default(),
863 &client.state_store_lock().lock().await,
864 )
865 .await
866 .expect("Failed to process sync");
867
868 let client_room = client.get_room(room_id).expect("No room found");
871 assert!(client_room.name().is_none());
872 assert_eq!(
873 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
874 "Empty Room"
875 );
876 assert_eq!(client_room.state(), RoomState::Joined);
877
878 assert!(sync_resp.rooms.joined.contains_key(room_id));
880 assert!(!sync_resp.rooms.left.contains_key(room_id));
881 assert!(!sync_resp.rooms.invited.contains_key(room_id));
882 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
883 }
884
885 #[async_test]
886 async fn test_room_name_event() {
887 let client = logged_in_base_client(None).await;
889 let room_id = room_id!("!r:e.uk");
890
891 let mut room = http::response::Room::new();
894
895 room.name = Some("little room".to_owned());
896 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
897
898 let response = response_with_room(room_id, room);
899 client
900 .process_sliding_sync(
901 &response,
902 &RequestedRequiredStates::default(),
903 &client.state_store_lock().lock().await,
904 )
905 .await
906 .expect("Failed to process sync");
907
908 let client_room = client.get_room(room_id).expect("No room found");
910 assert_eq!(client_room.name().as_deref(), Some("The Name"));
911 assert_eq!(
912 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
913 "The Name"
914 );
915 }
916
917 #[async_test]
918 async fn test_missing_invited_room_name_event() {
919 let client = logged_in_base_client(None).await;
921 let room_id = room_id!("!r:e.uk");
922 let user_id = user_id!("@w:e.uk");
923 let inviter = user_id!("@john:mastodon.org");
924
925 let mut room = http::response::Room::new();
928 set_room_invited(&mut room, inviter, user_id);
929 room.name = Some("name from sliding sync response".to_owned());
930 let response = response_with_room(room_id, room);
931 let sync_resp = client
932 .process_sliding_sync(
933 &response,
934 &RequestedRequiredStates::default(),
935 &client.state_store_lock().lock().await,
936 )
937 .await
938 .expect("Failed to process sync");
939
940 let client_room = client.get_room(room_id).expect("No room found");
942 assert!(client_room.name().is_none());
943
944 assert_eq!(client_room.compute_display_name().await.unwrap().into_inner().to_string(), "w");
946
947 assert_eq!(client_room.state(), RoomState::Invited);
948
949 assert!(!sync_resp.rooms.joined.contains_key(room_id));
951 assert!(!sync_resp.rooms.left.contains_key(room_id));
952 assert!(sync_resp.rooms.invited.contains_key(room_id));
953 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
954 }
955
956 #[async_test]
957 async fn test_invited_room_name_event() {
958 let client = logged_in_base_client(None).await;
960 let room_id = room_id!("!r:e.uk");
961 let user_id = user_id!("@w:e.uk");
962 let inviter = user_id!("@john:mastodon.org");
963
964 let mut room = http::response::Room::new();
967
968 set_room_invited(&mut room, inviter, user_id);
969
970 room.name = Some("name from sliding sync response".to_owned());
971 set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
972
973 let response = response_with_room(room_id, room);
974 client
975 .process_sliding_sync(
976 &response,
977 &RequestedRequiredStates::default(),
978 &client.state_store_lock().lock().await,
979 )
980 .await
981 .expect("Failed to process sync");
982
983 let client_room = client.get_room(room_id).expect("No room found");
985 assert_eq!(client_room.name().as_deref(), Some("The Name"));
986 assert_eq!(
987 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
988 "The Name"
989 );
990 }
991
992 #[async_test]
993 async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
994 let client = logged_in_base_client(None).await;
996 let room_id = room_id!("!r:e.uk");
997 let user_id = client.session_meta().unwrap().user_id.to_owned();
998
999 let mut room = http::response::Room::new();
1002 set_room_knocked(&mut room, &user_id);
1003
1004 let response = response_with_room(room_id, room);
1005 client
1006 .process_sliding_sync(
1007 &response,
1008 &RequestedRequiredStates::default(),
1009 &client.state_store_lock().lock().await,
1010 )
1011 .await
1012 .expect("Failed to process sync");
1013
1014 let client_room = client.get_room(room_id).expect("No room found");
1016 assert_eq!(client_room.state(), RoomState::Knocked);
1017 }
1018
1019 #[async_test]
1020 async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room()
1021 {
1022 let client = logged_in_base_client(None).await;
1024 let room_id = room_id!("!r:e.uk");
1025 let user_id = user_id!("@w:e.uk");
1026
1027 let mut room = http::response::Room::new();
1029 set_room_knocked(&mut room, user_id);
1030
1031 let response = response_with_room(room_id, room);
1032 client
1033 .process_sliding_sync(
1034 &response,
1035 &RequestedRequiredStates::default(),
1036 &client.state_store_lock().lock().await,
1037 )
1038 .await
1039 .expect("Failed to process sync");
1040
1041 let client_room = client.get_room(room_id).expect("No room found");
1044 assert_eq!(client_room.state(), RoomState::Invited);
1045 }
1046
1047 #[async_test]
1048 async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room()
1049 {
1050 let client = logged_in_base_client(None).await;
1052 let room_id = room_id!("!r:e.uk");
1053 let user_id = client.session_meta().unwrap().user_id.to_owned();
1054
1055 let mut room = http::response::Room::new();
1057 let event = Raw::new(&json!({
1058 "type": "m.room.member",
1059 "sender": user_id,
1060 "content": {
1061 "is_direct": true,
1062 "membership": "join",
1063 },
1064 "state_key": user_id,
1065 }))
1066 .expect("Failed to make raw event")
1067 .cast_unchecked();
1068 room.invite_state = Some(vec![event]);
1069
1070 let response = response_with_room(room_id, room);
1071 client
1072 .process_sliding_sync(
1073 &response,
1074 &RequestedRequiredStates::default(),
1075 &client.state_store_lock().lock().await,
1076 )
1077 .await
1078 .expect("Failed to process sync");
1079
1080 let client_room = client.get_room(room_id).expect("No room found");
1082 assert_eq!(client_room.state(), RoomState::Invited);
1083 }
1084
1085 #[async_test]
1086 async fn test_left_a_room_from_required_state_event() {
1087 let client = logged_in_base_client(None).await;
1089 let room_id = room_id!("!r:e.uk");
1090 let user_id = user_id!("@u:e.uk");
1091
1092 let mut room = http::response::Room::new();
1094 set_room_joined(&mut room, user_id);
1095 let response = response_with_room(room_id, room);
1096 client
1097 .process_sliding_sync(
1098 &response,
1099 &RequestedRequiredStates::default(),
1100 &client.state_store_lock().lock().await,
1101 )
1102 .await
1103 .expect("Failed to process sync");
1104 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1105
1106 let mut room = http::response::Room::new();
1108 set_room_left(&mut room, user_id);
1109 let response = response_with_room(room_id, room);
1110 let sync_resp = client
1111 .process_sliding_sync(
1112 &response,
1113 &RequestedRequiredStates::default(),
1114 &client.state_store_lock().lock().await,
1115 )
1116 .await
1117 .expect("Failed to process sync");
1118
1119 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1121
1122 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1124 assert!(sync_resp.rooms.left.contains_key(room_id));
1125 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1126 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1127 }
1128
1129 #[async_test]
1130 async fn test_kick_or_ban_updates_room_to_left() {
1131 for membership in [MembershipState::Leave, MembershipState::Ban] {
1132 let room_id = room_id!("!r:e.uk");
1133 let user_a_id = user_id!("@a:e.uk");
1134 let user_b_id = user_id!("@b:e.uk");
1135 let client = logged_in_base_client(Some(user_a_id)).await;
1136
1137 let mut room = http::response::Room::new();
1139 set_room_joined(&mut room, user_a_id);
1140 let response = response_with_room(room_id, room);
1141 client
1142 .process_sliding_sync(
1143 &response,
1144 &RequestedRequiredStates::default(),
1145 &client.state_store_lock().lock().await,
1146 )
1147 .await
1148 .expect("Failed to process sync");
1149 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1150
1151 let mut room = http::response::Room::new();
1153 room.required_state.push(make_state_event(
1154 user_b_id,
1155 user_a_id.as_str(),
1156 RoomMemberEventContent::new(membership.clone()),
1157 None,
1158 ));
1159 let response = response_with_room(room_id, room);
1160 let sync_resp = client
1161 .process_sliding_sync(
1162 &response,
1163 &RequestedRequiredStates::default(),
1164 &client.state_store_lock().lock().await,
1165 )
1166 .await
1167 .expect("Failed to process sync");
1168
1169 match membership {
1170 MembershipState::Leave => {
1171 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1173 }
1174 MembershipState::Ban => {
1175 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1177 }
1178 _ => panic!("Unexpected membership state found: {membership}"),
1179 }
1180
1181 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1183 assert!(sync_resp.rooms.left.contains_key(room_id));
1184 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1185 assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1186 }
1187 }
1188
1189 #[async_test]
1190 async fn test_left_a_room_from_timeline_state_event() {
1191 let client = logged_in_base_client(None).await;
1193 let room_id = room_id!("!r:e.uk");
1194 let user_id = user_id!("@u:e.uk");
1195
1196 let mut room = http::response::Room::new();
1198 set_room_joined(&mut room, user_id);
1199 let response = response_with_room(room_id, room);
1200 client
1201 .process_sliding_sync(
1202 &response,
1203 &RequestedRequiredStates::default(),
1204 &client.state_store_lock().lock().await,
1205 )
1206 .await
1207 .expect("Failed to process sync");
1208 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1209
1210 let mut room = http::response::Room::new();
1212 set_room_left_as_timeline_event(&mut room, user_id);
1213 let response = response_with_room(room_id, room);
1214 client
1215 .process_sliding_sync(
1216 &response,
1217 &RequestedRequiredStates::default(),
1218 &client.state_store_lock().lock().await,
1219 )
1220 .await
1221 .expect("Failed to process sync");
1222
1223 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1226 }
1227
1228 #[async_test]
1229 async fn test_can_be_reinvited_to_a_left_room() {
1230 let client = logged_in_base_client(None).await;
1234 let room_id = room_id!("!r:e.uk");
1235 let user_id = user_id!("@u:e.uk");
1236
1237 let mut room = http::response::Room::new();
1239 set_room_joined(&mut room, user_id);
1240 let response = response_with_room(room_id, room);
1241 client
1242 .process_sliding_sync(
1243 &response,
1244 &RequestedRequiredStates::default(),
1245 &client.state_store_lock().lock().await,
1246 )
1247 .await
1248 .expect("Failed to process sync");
1249 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1251
1252 let mut room = http::response::Room::new();
1254 set_room_left(&mut room, user_id);
1255 let response = response_with_room(room_id, room);
1256 client
1257 .process_sliding_sync(
1258 &response,
1259 &RequestedRequiredStates::default(),
1260 &client.state_store_lock().lock().await,
1261 )
1262 .await
1263 .expect("Failed to process sync");
1264 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1266
1267 let mut room = http::response::Room::new();
1269 set_room_invited(&mut room, user_id, user_id);
1270 let response = response_with_room(room_id, room);
1271 client
1272 .process_sliding_sync(
1273 &response,
1274 &RequestedRequiredStates::default(),
1275 &client.state_store_lock().lock().await,
1276 )
1277 .await
1278 .expect("Failed to process sync");
1279
1280 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1282 }
1283
1284 #[async_test]
1285 async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1286 let room_id = room_id!("!r:e.uk");
1287 let user_a_id = user_id!("@a:e.uk");
1288 let user_b_id = user_id!("@b:e.uk");
1289
1290 let client = logged_in_base_client(None).await;
1292 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1293
1294 assert!(
1296 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1297 );
1298 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1299
1300 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1302
1303 assert!(
1308 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1309 );
1310 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1311 }
1312
1313 #[async_test]
1314 async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets()
1315 {
1316 let room_id = room_id!("!r:e.uk");
1317 let user_a_id = user_id!("@a:e.uk");
1318 let user_b_id = user_id!("@b:e.uk");
1319
1320 let client = logged_in_base_client(None).await;
1322 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1323
1324 assert!(
1326 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1327 );
1328 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1329
1330 update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1332
1333 assert!(
1338 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1339 );
1340 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1341 }
1342
1343 #[async_test]
1344 async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1345 let room_id = room_id!("!r:bar.org");
1346 let user_a_id = user_id!("@a:bar.org");
1347 let user_b_id = user_id!("@b:bar.org");
1348
1349 let client = logged_in_base_client(None).await;
1351 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1352
1353 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1355
1356 assert!(
1358 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1359 );
1360 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1361
1362 let room = client.get_room(room_id).unwrap();
1363
1364 assert_eq!(room.active_members_count(), 2);
1365 assert_eq!(room.joined_members_count(), 2);
1366 assert_eq!(room.invited_members_count(), 0);
1367 }
1368
1369 #[async_test]
1370 async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1371 let room_id = room_id!("!r:bar.org");
1372 let user_a_id = user_id!("@a:bar.org");
1373 let user_b_id = user_id!("@b:bar.org");
1374
1375 let client = logged_in_base_client(None).await;
1377 create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1378
1379 assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1381
1382 assert!(
1384 direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id))
1385 );
1386 assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1387
1388 let room = client.get_room(room_id).unwrap();
1389
1390 assert_eq!(room.active_members_count(), 2);
1391 assert_eq!(room.joined_members_count(), 1);
1392 assert_eq!(room.invited_members_count(), 1);
1393 }
1394
1395 #[async_test]
1396 async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1397 let client = logged_in_base_client(None).await;
1399 let room_id = room_id!("!r:e.uk");
1400
1401 let room = {
1403 let mut room = http::response::Room::new();
1404 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1405
1406 room
1407 };
1408 let response = response_with_room(room_id, room);
1409 client
1410 .process_sliding_sync(
1411 &response,
1412 &RequestedRequiredStates::default(),
1413 &client.state_store_lock().lock().await,
1414 )
1415 .await
1416 .expect("Failed to process sync");
1417
1418 let client_room = client.get_room(room_id).expect("No room found");
1420 assert_eq!(
1421 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1422 "med1"
1423 );
1424 }
1425
1426 #[async_test]
1427 async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1428 let client = logged_in_base_client(None).await;
1430 let room_id = room_id!("!r:e.uk");
1431
1432 let room = {
1436 let mut room = http::response::Room::new();
1437 room.avatar = JsOption::from_option(Some(owned_mxc_uri!("mxc://e.uk/med1")));
1438
1439 room
1440 };
1441 let response = response_with_room(room_id, room);
1442 client
1443 .process_sliding_sync(
1444 &response,
1445 &RequestedRequiredStates::default(),
1446 &client.state_store_lock().lock().await,
1447 )
1448 .await
1449 .expect("Failed to process sync");
1450
1451 let client_room = client.get_room(room_id).expect("No room found");
1453 assert_eq!(
1454 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1455 "med1"
1456 );
1457
1458 let room = http::response::Room::new();
1462 let response = response_with_room(room_id, room);
1463 client
1464 .process_sliding_sync(
1465 &response,
1466 &RequestedRequiredStates::default(),
1467 &client.state_store_lock().lock().await,
1468 )
1469 .await
1470 .expect("Failed to process sync");
1471
1472 let client_room = client.get_room(room_id).expect("No room found");
1474 assert_eq!(
1475 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1476 "med1"
1477 );
1478
1479 let room = {
1484 let mut room = http::response::Room::new();
1485 room.avatar = JsOption::Null;
1486
1487 room
1488 };
1489 let response = response_with_room(room_id, room);
1490 client
1491 .process_sliding_sync(
1492 &response,
1493 &RequestedRequiredStates::default(),
1494 &client.state_store_lock().lock().await,
1495 )
1496 .await
1497 .expect("Failed to process sync");
1498
1499 let client_room = client.get_room(room_id).expect("No room found");
1501 assert!(client_room.avatar_url().is_none());
1502 }
1503
1504 #[async_test]
1505 async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1506 let client = logged_in_base_client(None).await;
1508 let room_id = room_id!("!r:e.uk");
1509 let user_id = user_id!("@u:e.uk");
1510
1511 let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1513 let response = response_with_room(room_id, room);
1514 client
1515 .process_sliding_sync(
1516 &response,
1517 &RequestedRequiredStates::default(),
1518 &client.state_store_lock().lock().await,
1519 )
1520 .await
1521 .expect("Failed to process sync");
1522
1523 let client_room = client.get_room(room_id).expect("No room found");
1525 assert_eq!(
1526 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1527 "med1"
1528 );
1529 }
1530
1531 #[async_test]
1532 async fn test_invitation_room_is_added_to_client_and_invite_list() {
1533 let client = logged_in_base_client(None).await;
1535 let room_id = room_id!("!r:e.uk");
1536 let user_id = user_id!("@u:e.uk");
1537
1538 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1539
1540 let mut room = http::response::Room::new();
1542 set_room_invited(&mut room, user_id, user_id);
1543 let response = response_with_room(room_id, room);
1544 let sync_resp = client
1545 .process_sliding_sync(
1546 &response,
1547 &RequestedRequiredStates::default(),
1548 &client.state_store_lock().lock().await,
1549 )
1550 .await
1551 .expect("Failed to process sync");
1552
1553 let client_room = client.get_room(room_id).expect("No room found");
1555 assert_eq!(client_room.room_id(), room_id);
1556 assert_eq!(client_room.state(), RoomState::Invited);
1557
1558 assert!(!sync_resp.rooms.invited[room_id].invite_state.is_empty());
1560 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1561
1562 assert_matches!(
1563 room_info_notable_update.recv().await,
1564 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1565 assert_eq!(received_room_id, room_id);
1566 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1568 }
1569 );
1570 assert_matches!(
1571 room_info_notable_update.recv().await,
1572 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1573 assert_eq!(received_room_id, room_id);
1574 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1576 }
1577 );
1578 assert!(room_info_notable_update.is_empty());
1579 }
1580
1581 #[async_test]
1582 async fn test_knock_room_is_added_to_client_and_knock_list() {
1583 let client = logged_in_base_client(None).await;
1585 let room_id = room_id!("!r:e.uk");
1586 let user_id = user_id!("@u:e.uk");
1587
1588 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1589
1590 let mut room = http::response::Room::new();
1592 set_room_knocked(&mut room, user_id);
1593 let response = response_with_room(room_id, room);
1594 let sync_resp = client
1595 .process_sliding_sync(
1596 &response,
1597 &RequestedRequiredStates::default(),
1598 &client.state_store_lock().lock().await,
1599 )
1600 .await
1601 .expect("Failed to process sync");
1602
1603 let client_room = client.get_room(room_id).expect("No room found");
1605 assert_eq!(client_room.room_id(), room_id);
1606 assert_eq!(client_room.state(), RoomState::Knocked);
1607
1608 assert!(!sync_resp.rooms.knocked[room_id].knock_state.is_empty());
1610 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1611
1612 assert_matches!(
1613 room_info_notable_update.recv().await,
1614 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1615 assert_eq!(received_room_id, room_id);
1616 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1618 }
1619 );
1620 assert_matches!(
1621 room_info_notable_update.recv().await,
1622 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1623 assert_eq!(received_room_id, room_id);
1624 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1626 }
1627 );
1628 assert!(room_info_notable_update.is_empty());
1629 }
1630
1631 #[async_test]
1632 async fn test_invited_room_stays_invited_when_a_later_response_has_no_invite_state() {
1633 let client = logged_in_base_client(None).await;
1635 let room_id = room_id!("!r:e.uk");
1636 let user_id = user_id!("@u:e.uk");
1637
1638 let mut room = http::response::Room::new();
1639 set_room_invited(&mut room, user_id, user_id);
1640 let response = response_with_room(room_id, room);
1641 client
1642 .process_sliding_sync(
1643 &response,
1644 &RequestedRequiredStates::default(),
1645 &client.state_store_lock().lock().await,
1646 )
1647 .await
1648 .expect("Failed to process sync");
1649
1650 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1652
1653 let response = response_with_room(room_id, http::response::Room::new());
1656 let sync_resp = client
1657 .process_sliding_sync(
1658 &response,
1659 &RequestedRequiredStates::default(),
1660 &client.state_store_lock().lock().await,
1661 )
1662 .await
1663 .expect("Failed to process sync");
1664
1665 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1667 assert!(sync_resp.rooms.invited.contains_key(room_id));
1668 assert!(!sync_resp.rooms.joined.contains_key(room_id));
1669 }
1670
1671 #[async_test]
1672 async fn test_invited_room_becomes_joined_from_required_state_event() {
1673 let client = logged_in_base_client(None).await;
1675 let room_id = room_id!("!r:e.uk");
1676 let user_id = user_id!("@u:e.uk");
1677
1678 let mut room = http::response::Room::new();
1679 set_room_invited(&mut room, user_id, user_id);
1680 let response = response_with_room(room_id, room);
1681 client
1682 .process_sliding_sync(
1683 &response,
1684 &RequestedRequiredStates::default(),
1685 &client.state_store_lock().lock().await,
1686 )
1687 .await
1688 .expect("Failed to process sync");
1689
1690 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1692
1693 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1694
1695 let mut room = http::response::Room::new();
1698 set_room_joined(&mut room, user_id);
1699 let response = response_with_room(room_id, room);
1700 let sync_resp = client
1701 .process_sliding_sync(
1702 &response,
1703 &RequestedRequiredStates::default(),
1704 &client.state_store_lock().lock().await,
1705 )
1706 .await
1707 .expect("Failed to process sync");
1708
1709 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1711
1712 assert!(sync_resp.rooms.joined.contains_key(room_id));
1713 assert!(!sync_resp.rooms.invited.contains_key(room_id));
1714
1715 assert_matches!(
1717 room_info_notable_update.recv().await,
1718 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1719 assert_eq!(received_room_id, room_id);
1720 assert!(reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
1721 }
1722 );
1723 }
1724
1725 #[async_test]
1726 async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1727 let client = logged_in_base_client(None).await;
1729 let room_id = room_id!("!r:e.uk");
1730 let user_id = user_id!("@u:e.uk");
1731
1732 let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1735 set_room_invited(&mut room, user_id, user_id);
1736 let response = response_with_room(room_id, room);
1737 client
1738 .process_sliding_sync(
1739 &response,
1740 &RequestedRequiredStates::default(),
1741 &client.state_store_lock().lock().await,
1742 )
1743 .await
1744 .expect("Failed to process sync");
1745
1746 let client_room = client.get_room(room_id).expect("No room found");
1748 assert_eq!(
1749 client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1750 "med1"
1751 );
1752 }
1753
1754 #[async_test]
1755 async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response()
1756 {
1757 let client = logged_in_base_client(None).await;
1759 let room_id = room_id!("!r:e.uk");
1760 let user_id = user_id!("@u:e.uk");
1761 let room_alias_id = room_alias_id!("#myroom:e.uk");
1762
1763 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1766 set_room_invited(&mut room, user_id, user_id);
1767 let response = response_with_room(room_id, room);
1768 client
1769 .process_sliding_sync(
1770 &response,
1771 &RequestedRequiredStates::default(),
1772 &client.state_store_lock().lock().await,
1773 )
1774 .await
1775 .expect("Failed to process sync");
1776
1777 let client_room = client.get_room(room_id).expect("No room found");
1779 assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1780 }
1781
1782 #[async_test]
1783 async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1784 let client = logged_in_base_client(None).await;
1786 let room_id = room_id!("!r:e.uk");
1787 let user_id = user_id!("@u:e.uk");
1788 let room_alias_id = room_alias_id!("#myroom:e.uk");
1789
1790 let mut room = room_with_canonical_alias(room_alias_id, user_id);
1793 room.name = Some("This came from the server".to_owned());
1794 let response = response_with_room(room_id, room);
1795 client
1796 .process_sliding_sync(
1797 &response,
1798 &RequestedRequiredStates::default(),
1799 &client.state_store_lock().lock().await,
1800 )
1801 .await
1802 .expect("Failed to process sync");
1803
1804 let client_room = client.get_room(room_id).expect("No room found");
1807 assert_eq!(
1808 client_room.compute_display_name().await.unwrap().into_inner().to_string(),
1809 "myroom"
1810 );
1811 assert!(client_room.name().is_none());
1812 }
1813
1814 #[async_test]
1815 async fn test_display_name_is_cached_and_emits_a_notable_update_reason() {
1816 let client = logged_in_base_client(None).await;
1817 let user_id = user_id!("@u:e.uk");
1818 let room_id = room_id!("!r:e.uk");
1819
1820 let mut room_info_notable_update = client.room_info_notable_update_receiver();
1821
1822 let room = room_with_name("Hello World", user_id);
1823 let response = response_with_room(room_id, room);
1824 client
1825 .process_sliding_sync(
1826 &response,
1827 &RequestedRequiredStates::default(),
1828 &client.state_store_lock().lock().await,
1829 )
1830 .await
1831 .expect("Failed to process sync");
1832
1833 let room = client.get_room(room_id).expect("No room found");
1834 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1835
1836 assert_matches!(
1837 room_info_notable_update.recv().await,
1838 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1839 assert_eq!(received_room_id, room_id);
1840 assert!(reasons.contains(RoomInfoNotableUpdateReasons::NONE));
1841 }
1842 );
1843 assert_matches!(
1844 room_info_notable_update.recv().await,
1845 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
1846 assert_eq!(received_room_id, room_id);
1847 assert!(reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
1849 }
1850 );
1851 assert!(room_info_notable_update.is_empty());
1852 }
1853
1854 #[async_test]
1855 async fn test_display_name_is_persisted_from_sliding_sync() {
1856 let user_id = user_id!("@u:e.uk");
1857 let room_id = room_id!("!r:e.uk");
1858 let session_meta = SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() };
1859 let state_store;
1860
1861 {
1862 let client = {
1863 let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1864 state_store = store.state_store.clone();
1865
1866 let client =
1867 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1868 client
1869 .activate(
1870 session_meta.clone(),
1871 RoomLoadSettings::default(),
1872 #[cfg(feature = "e2e-encryption")]
1873 None,
1874 )
1875 .await
1876 .expect("`activate` failed!");
1877
1878 client
1879 };
1880
1881 let room = room_with_name("Hello World", user_id);
1884 let response = response_with_room(room_id, room);
1885 client
1886 .process_sliding_sync(
1887 &response,
1888 &RequestedRequiredStates::default(),
1889 &client.state_store_lock().lock().await,
1890 )
1891 .await
1892 .expect("Failed to process sync");
1893
1894 let room = client.get_room(room_id).expect("No room found");
1895 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1896 }
1897
1898 {
1899 let client = {
1900 let mut store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
1901 store.state_store = state_store;
1902 let client =
1903 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
1904 client
1905 .activate(
1906 session_meta,
1907 RoomLoadSettings::default(),
1908 #[cfg(feature = "e2e-encryption")]
1909 None,
1910 )
1911 .await
1912 .expect("`activate` failed!");
1913
1914 client
1915 };
1916
1917 let room = client.get_room(room_id).expect("No room found");
1918 assert_eq!(room.cached_display_name().unwrap().to_string(), "Hello World");
1919 }
1920 }
1921
1922 #[async_test]
1923 async fn test_compute_heroes_from_sliding_sync() {
1924 let client = logged_in_base_client(None).await;
1926 let room_id = room_id!("!r:e.uk");
1927 let gordon = owned_user_id!("@gordon:e.uk");
1928 let alice = owned_user_id!("@alice:e.uk");
1929
1930 let mut room = http::response::Room::new();
1933 room.heroes = Some(vec![
1934 assign!(http::response::Hero::new(gordon), {
1935 name: Some("Gordon".to_owned()),
1936 }),
1937 assign!(http::response::Hero::new(alice), {
1938 name: Some("Alice".to_owned()),
1939 avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1940 }),
1941 ]);
1942 let response = response_with_room(room_id, room);
1943 let _sync_resp = client
1944 .process_sliding_sync(
1945 &response,
1946 &RequestedRequiredStates::default(),
1947 &client.state_store_lock().lock().await,
1948 )
1949 .await
1950 .expect("Failed to process sync");
1951
1952 let client_room = client.get_room(room_id).expect("No room found");
1954 assert_eq!(client_room.room_id(), room_id);
1955 assert_eq!(client_room.state(), RoomState::Joined);
1956
1957 assert_eq!(
1959 client_room.clone_info().summary.heroes(),
1960 &[
1961 RoomHero {
1962 user_id: owned_user_id!("@gordon:e.uk"),
1963 display_name: Some("Gordon".to_owned()),
1964 avatar_url: None
1965 },
1966 RoomHero {
1967 user_id: owned_user_id!("@alice:e.uk"),
1968 display_name: Some("Alice".to_owned()),
1969 avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1970 },
1971 ]
1972 );
1973 }
1974
1975 #[cfg(feature = "unstable-msc4426")]
1976 #[async_test]
1977 async fn test_hero_global_profile_update_triggers_notable_update() {
1978 let client = logged_in_base_client(None).await;
1979 let room_id = room_id!("!r:e.uk");
1980 let alice = owned_user_id!("@alice:e.uk");
1981
1982 let mut room = http::response::Room::new();
1984 room.heroes = Some(vec![assign!(http::response::Hero::new(alice.clone()), {
1985 name: Some("Alice".to_owned()),
1986 })]);
1987 let response = response_with_room(room_id, room);
1988 client
1989 .process_sliding_sync(
1990 &response,
1991 &RequestedRequiredStates::default(),
1992 &client.state_store_lock().lock().await,
1993 )
1994 .await
1995 .expect("Failed to process sync");
1996
1997 let room = client.get_room(room_id).expect("The room should be known");
1998 let mut room_info_subscriber = room.subscribe_info();
1999 let mut room_info_notable_update = client.room_info_notable_update_receiver();
2000
2001 assert_pending!(room_info_subscriber);
2002
2003 let mut response = http::Response::new("1".to_owned());
2006 response.extensions.profiles.users.insert(
2007 alice.clone(),
2008 make_profile_update(ProfileFieldName::Status, json!({ "text": "Away", "emoji": "🌴" })),
2009 );
2010 client
2011 .process_sliding_sync(
2012 &response,
2013 &RequestedRequiredStates::default(),
2014 &client.state_store_lock().lock().await,
2015 )
2016 .await
2017 .expect("Failed to process sync");
2018
2019 assert_matches!(
2022 room_info_notable_update.recv().await,
2023 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
2024 assert_eq!(received_room_id, room_id);
2025 assert!(reasons.contains(RoomInfoNotableUpdateReasons::HEROES));
2026 }
2027 );
2028 assert!(room_info_notable_update.is_empty());
2029
2030 assert_ready!(room_info_subscriber);
2033 assert_pending!(room_info_subscriber);
2034
2035 let heroes = room.heroes().await;
2037 assert_eq!(heroes.len(), 1);
2038 assert_eq!(heroes[0].user_id, alice);
2039 let status = heroes[0].status.as_ref().expect("Alice's status should be set");
2040 assert_eq!(status.text, "Away");
2041 assert_eq!(status.emoji, "🌴");
2042 }
2043
2044 #[async_test]
2045 async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2046 let client = logged_in_base_client(None).await;
2048 let room_id = room_id!("!r:e.uk");
2049
2050 let room = assign!(http::response::Room::new(), {
2053 bump_stamp: Some(42u32.into()),
2054 });
2055 let response = response_with_room(room_id, room);
2056 client
2057 .process_sliding_sync(
2058 &response,
2059 &RequestedRequiredStates::default(),
2060 &client.state_store_lock().lock().await,
2061 )
2062 .await
2063 .expect("Failed to process sync");
2064
2065 let client_room = client.get_room(room_id).expect("No room found");
2067 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2068 }
2069
2070 #[async_test]
2071 async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2072 let client = logged_in_base_client(None).await;
2074 let room_id = room_id!("!r:e.uk");
2075
2076 {
2077 let room = assign!(http::response::Room::new(), {
2080 bump_stamp: Some(42u32.into()),
2081 });
2082 let response = response_with_room(room_id, room);
2083 client
2084 .process_sliding_sync(
2085 &response,
2086 &RequestedRequiredStates::default(),
2087 &client.state_store_lock().lock().await,
2088 )
2089 .await
2090 .expect("Failed to process sync");
2091
2092 let client_room = client.get_room(room_id).expect("No room found");
2094 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2095 }
2096
2097 {
2098 let room = assign!(http::response::Room::new(), {
2101 bump_stamp: None,
2102 });
2103 let response = response_with_room(room_id, room);
2104 client
2105 .process_sliding_sync(
2106 &response,
2107 &RequestedRequiredStates::default(),
2108 &client.state_store_lock().lock().await,
2109 )
2110 .await
2111 .expect("Failed to process sync");
2112
2113 let client_room = client.get_room(room_id).expect("No room found");
2115 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42.into());
2116 }
2117
2118 {
2119 let room = assign!(http::response::Room::new(), {
2122 bump_stamp: Some(153u32.into()),
2123 });
2124 let response = response_with_room(room_id, room);
2125 client
2126 .process_sliding_sync(
2127 &response,
2128 &RequestedRequiredStates::default(),
2129 &client.state_store_lock().lock().await,
2130 )
2131 .await
2132 .expect("Failed to process sync");
2133
2134 let client_room = client.get_room(room_id).expect("No room found");
2136 assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153.into());
2137 }
2138 }
2139
2140 #[async_test]
2141 async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2142 let client = logged_in_base_client(None).await;
2144 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2145 let room_id = room_id!("!r:e.uk");
2146
2147 let room = assign!(http::response::Room::new(), {
2150 bump_stamp: Some(42u32.into()),
2151 });
2152 let response = response_with_room(room_id, room);
2153 client
2154 .process_sliding_sync(
2155 &response,
2156 &RequestedRequiredStates::default(),
2157 &client.state_store_lock().lock().await,
2158 )
2159 .await
2160 .expect("Failed to process sync");
2161
2162 assert_matches!(
2165 room_info_notable_update_stream.recv().await,
2166 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2167 assert_eq!(received_room_id, room_id);
2168 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2169 }
2170 );
2171 assert_matches!(
2172 room_info_notable_update_stream.recv().await,
2173 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2174 assert_eq!(received_room_id, room_id);
2175 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
2176 }
2177 );
2178 assert!(room_info_notable_update_stream.is_empty());
2179
2180 let room = assign!(http::response::Room::new(), {
2183 bump_stamp: Some(43u32.into()),
2184 });
2185 let response = response_with_room(room_id, room);
2186 client
2187 .process_sliding_sync(
2188 &response,
2189 &RequestedRequiredStates::default(),
2190 &client.state_store_lock().lock().await,
2191 )
2192 .await
2193 .expect("Failed to process sync");
2194
2195 assert_matches!(
2197 room_info_notable_update_stream.recv().await,
2198 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2199 assert_eq!(received_room_id, room_id);
2200 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2201 }
2202 );
2203 assert!(room_info_notable_update_stream.is_empty());
2204 }
2205
2206 #[async_test]
2207 async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2208 let client = logged_in_base_client(None).await;
2210 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2211
2212 let room_id = room_id!("!r:e.uk");
2214 let room = http::response::Room::new();
2215 let response = response_with_room(room_id, room);
2216 client
2217 .process_sliding_sync(
2218 &response,
2219 &RequestedRequiredStates::default(),
2220 &client.state_store_lock().lock().await,
2221 )
2222 .await
2223 .expect("Failed to process sync");
2224
2225 assert_matches!(
2227 room_info_notable_update_stream.recv().await,
2228 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2229 assert_eq!(received_room_id, room_id);
2230 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
2231 }
2232 );
2233 assert_matches!(
2234 room_info_notable_update_stream.recv().await,
2235 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2236 assert_eq!(received_room_id, room_id);
2237 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME));
2238 }
2239 );
2240
2241 let room_id = room_id!("!r:e.uk");
2244 let events = vec![
2245 Raw::from_json_string(
2246 json!({
2247 "type": "m.room.member",
2248 "event_id": "$3",
2249 "content": { "membership": "join" },
2250 "sender": "@u:h.uk",
2251 "origin_server_ts": 12344445,
2252 "state_key": "@u:e.uk",
2253 })
2254 .to_string(),
2255 )
2256 .unwrap(),
2257 ];
2258 let room = assign!(http::response::Room::new(), {
2259 required_state: events,
2260 });
2261 let response = response_with_room(room_id, room);
2262 client
2263 .process_sliding_sync(
2264 &response,
2265 &RequestedRequiredStates::default(),
2266 &client.state_store_lock().lock().await,
2267 )
2268 .await
2269 .expect("Failed to process sync");
2270
2271 assert_matches!(
2274 room_info_notable_update_stream.recv().await,
2275 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2276 assert_eq!(received_room_id, room_id);
2277 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE));
2278 }
2279 );
2280 assert!(room_info_notable_update_stream.is_empty());
2281
2282 let events = vec![
2283 Raw::from_json_string(
2284 json!({
2285 "type": "m.room.member",
2286 "event_id": "$3",
2287 "content": { "membership": "leave" },
2288 "sender": "@u:h.uk",
2289 "origin_server_ts": 12344445,
2290 "state_key": "@u:e.uk",
2291 })
2292 .to_string(),
2293 )
2294 .unwrap(),
2295 ];
2296 let room = assign!(http::response::Room::new(), {
2297 required_state: events,
2298 });
2299 let response = response_with_room(room_id, room);
2300 client
2301 .process_sliding_sync(
2302 &response,
2303 &RequestedRequiredStates::default(),
2304 &client.state_store_lock().lock().await,
2305 )
2306 .await
2307 .expect("Failed to process sync");
2308
2309 assert_matches!(
2311 room_info_notable_update_stream.recv().await,
2312 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2313 assert_eq!(received_room_id, room_id);
2314 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2315 }
2316 );
2317 assert!(room_info_notable_update_stream.is_empty());
2318 }
2319
2320 #[async_test]
2321 async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2322 let client = logged_in_base_client(None).await;
2324 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2325
2326 let room_id = room_id!("!r:e.uk");
2328 let room = http::response::Room::new();
2329 let response = response_with_room(room_id, room);
2330 client
2331 .process_sliding_sync(
2332 &response,
2333 &RequestedRequiredStates::default(),
2334 &client.state_store_lock().lock().await,
2335 )
2336 .await
2337 .expect("Failed to process sync");
2338
2339 assert_matches!(
2342 room_info_notable_update_stream.recv().await,
2343 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2344 assert_eq!(received_room_id, room_id);
2345 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2346 }
2347 );
2348 assert_matches!(
2349 room_info_notable_update_stream.recv().await,
2350 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2351 assert_eq!(received_room_id, room_id);
2352 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2353 }
2354 );
2355 assert!(room_info_notable_update_stream.is_empty());
2356
2357 let room_id = room_id!("!r:e.uk");
2360 let room_account_data_events = vec![
2361 Raw::from_json_string(
2362 json!({
2363 "type": "m.marked_unread",
2364 "event_id": "$1",
2365 "content": { "unread": true },
2366 "sender": client.session_meta().unwrap().user_id,
2367 "origin_server_ts": 12344445,
2368 })
2369 .to_string(),
2370 )
2371 .unwrap(),
2372 ];
2373 let mut response = response_with_room(room_id, http::response::Room::new());
2374 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2375
2376 client
2377 .process_sliding_sync(
2378 &response,
2379 &RequestedRequiredStates::default(),
2380 &client.state_store_lock().lock().await,
2381 )
2382 .await
2383 .expect("Failed to process sync");
2384
2385 assert_matches!(
2387 room_info_notable_update_stream.recv().await,
2388 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2389 assert_eq!(received_room_id, room_id);
2390 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2391 }
2392 );
2393
2394 client
2396 .process_sliding_sync(
2397 &response,
2398 &RequestedRequiredStates::default(),
2399 &client.state_store_lock().lock().await,
2400 )
2401 .await
2402 .expect("Failed to process sync");
2403
2404 assert_matches!(
2405 room_info_notable_update_stream.recv().await,
2406 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2407 assert_eq!(received_room_id, room_id);
2408 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2409 }
2410 );
2411 assert!(room_info_notable_update_stream.is_empty());
2412
2413 let room_account_data_events = vec![
2415 Raw::from_json_string(
2416 json!({
2417 "type": "m.marked_unread",
2418 "event_id": "$1",
2419 "content": { "unread": false },
2420 "sender": client.session_meta().unwrap().user_id,
2421 "origin_server_ts": 12344445,
2422 })
2423 .to_string(),
2424 )
2425 .unwrap(),
2426 ];
2427 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2428 client
2429 .process_sliding_sync(
2430 &response,
2431 &RequestedRequiredStates::default(),
2432 &client.state_store_lock().lock().await,
2433 )
2434 .await
2435 .expect("Failed to process sync");
2436
2437 assert_matches!(
2438 room_info_notable_update_stream.recv().await,
2439 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2440 assert_eq!(received_room_id, room_id);
2441 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2442 }
2443 );
2444 assert!(room_info_notable_update_stream.is_empty());
2445 }
2446
2447 #[async_test]
2448 async fn test_empty_room_account_data_does_not_create_a_room_update() {
2449 let client = logged_in_base_client(None).await;
2450
2451 let room_id_a = room_id!("!a:e.uk");
2452 let room_id_b = room_id!("!b:e.uk");
2453 let user_id = client.session_meta().unwrap().user_id.clone();
2454
2455 let mut response = http::Response::new("0".to_owned());
2456 for room_id in [room_id_a, room_id_b] {
2457 let mut room = http::response::Room::new();
2458 set_room_joined(&mut room, &user_id);
2459 response.rooms.insert(room_id.to_owned(), room);
2460 }
2461 client
2462 .process_sliding_sync(
2463 &response,
2464 &RequestedRequiredStates::default(),
2465 &client.state_store_lock().lock().await,
2466 )
2467 .await
2468 .expect("Failed to process sync");
2469
2470 let mut response = response_with_room(room_id_a, http::response::Room::new());
2471 response.extensions.account_data.rooms.insert(room_id_b.to_owned(), vec![]);
2472
2473 let sync_response = client
2474 .process_sliding_sync(
2475 &response,
2476 &RequestedRequiredStates::default(),
2477 &client.state_store_lock().lock().await,
2478 )
2479 .await
2480 .expect("Failed to process sync");
2481
2482 assert!(sync_response.rooms.joined.contains_key(room_id_a));
2483 assert!(!sync_response.rooms.joined.contains_key(room_id_b));
2484 assert!(sync_response.rooms.left.is_empty());
2485 }
2486
2487 #[async_test]
2488 async fn test_fully_read_marker_can_trigger_a_notable_update_reason() {
2489 let client = logged_in_base_client(None).await;
2491 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2492
2493 let room_id = room_id!("!r:e.uk");
2495 let room = http::response::Room::new();
2496 let response = response_with_room(room_id, room);
2497 client
2498 .process_sliding_sync(
2499 &response,
2500 &RequestedRequiredStates::default(),
2501 &client.state_store_lock().lock().await,
2502 )
2503 .await
2504 .expect("Failed to process sync");
2505
2506 assert_matches!(
2509 room_info_notable_update_stream.recv().await,
2510 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2511 assert_eq!(received_room_id, room_id);
2512 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2513 }
2514 );
2515 assert_matches!(
2516 room_info_notable_update_stream.recv().await,
2517 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2518 assert_eq!(received_room_id, room_id);
2519 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2520 }
2521 );
2522 assert!(room_info_notable_update_stream.is_empty());
2523
2524 let room_account_data_events = vec![
2527 Raw::from_json_string(
2528 json!({
2529 "type": "m.fully_read",
2530 "content": { "event_id": "$first" },
2531 })
2532 .to_string(),
2533 )
2534 .unwrap(),
2535 ];
2536 let mut response = response_with_room(room_id, http::response::Room::new());
2537 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2538
2539 client
2540 .process_sliding_sync(
2541 &response,
2542 &RequestedRequiredStates::default(),
2543 &client.state_store_lock().lock().await,
2544 )
2545 .await
2546 .expect("Failed to process sync");
2547
2548 assert_matches!(
2550 room_info_notable_update_stream.recv().await,
2551 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2552 assert_eq!(received_room_id, room_id);
2553 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2554 }
2555 );
2556
2557 let room = client.get_room(room_id).expect("room should exist");
2558 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$first"),);
2559
2560 client
2562 .process_sliding_sync(
2563 &response,
2564 &RequestedRequiredStates::default(),
2565 &client.state_store_lock().lock().await,
2566 )
2567 .await
2568 .expect("Failed to process sync");
2569
2570 assert_matches!(
2571 room_info_notable_update_stream.recv().await,
2572 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2573 assert_eq!(received_room_id, room_id);
2574 assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2575 }
2576 );
2577 assert!(room_info_notable_update_stream.is_empty());
2578
2579 let room_account_data_events = vec![
2581 Raw::from_json_string(
2582 json!({
2583 "type": "m.fully_read",
2584 "content": { "event_id": "$second" },
2585 })
2586 .to_string(),
2587 )
2588 .unwrap(),
2589 ];
2590 response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2591 client
2592 .process_sliding_sync(
2593 &response,
2594 &RequestedRequiredStates::default(),
2595 &client.state_store_lock().lock().await,
2596 )
2597 .await
2598 .expect("Failed to process sync");
2599
2600 assert_matches!(
2601 room_info_notable_update_stream.recv().await,
2602 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2603 assert_eq!(received_room_id, room_id);
2604 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::FULLY_READ), "{received_reasons:?}");
2605 }
2606 );
2607 assert_eq!(room.fully_read_event_id().as_deref().map(|id| id.as_str()), Some("$second"),);
2608 assert!(room_info_notable_update_stream.is_empty());
2609 }
2610
2611 #[async_test]
2612 async fn test_unstable_unread_marker_is_ignored_after_stable() {
2613 let client = logged_in_base_client(None).await;
2615 let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2616
2617 let room_id = room_id!("!r:e.uk");
2619 let room = http::response::Room::new();
2620 let response = response_with_room(room_id, room);
2621 client
2622 .process_sliding_sync(
2623 &response,
2624 &RequestedRequiredStates::default(),
2625 &client.state_store_lock().lock().await,
2626 )
2627 .await
2628 .expect("Failed to process sync");
2629
2630 assert_matches!(
2633 room_info_notable_update_stream.recv().await,
2634 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2635 assert_eq!(received_room_id, room_id);
2636 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2637 }
2638 );
2639 assert_matches!(
2640 room_info_notable_update_stream.recv().await,
2641 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2642 assert_eq!(received_room_id, room_id);
2643 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::DISPLAY_NAME), "{received_reasons:?}");
2644 }
2645 );
2646 assert!(room_info_notable_update_stream.is_empty());
2647
2648 let room_id = room_id!("!r:e.uk");
2651 let unstable_room_account_data_events = vec![
2652 Raw::from_json_string(
2653 json!({
2654 "type": "com.famedly.marked_unread",
2655 "event_id": "$1",
2656 "content": { "unread": true },
2657 "sender": client.session_meta().unwrap().user_id,
2658 "origin_server_ts": 12344445,
2659 })
2660 .to_string(),
2661 )
2662 .unwrap(),
2663 ];
2664 let mut response = response_with_room(room_id, http::response::Room::new());
2665 response
2666 .extensions
2667 .account_data
2668 .rooms
2669 .insert(room_id.to_owned(), unstable_room_account_data_events.clone());
2670
2671 client
2672 .process_sliding_sync(
2673 &response,
2674 &RequestedRequiredStates::default(),
2675 &client.state_store_lock().lock().await,
2676 )
2677 .await
2678 .expect("Failed to process sync");
2679
2680 assert_matches!(
2682 room_info_notable_update_stream.recv().await,
2683 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2684 assert_eq!(received_room_id, room_id);
2685 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2686 }
2687 );
2688 assert!(room_info_notable_update_stream.is_empty());
2689
2690 let stable_room_account_data_events = vec![
2693 Raw::from_json_string(
2694 json!({
2695 "type": "m.marked_unread",
2696 "event_id": "$1",
2697 "content": { "unread": false },
2698 "sender": client.session_meta().unwrap().user_id,
2699 "origin_server_ts": 12344445,
2700 })
2701 .to_string(),
2702 )
2703 .unwrap(),
2704 ];
2705 response
2706 .extensions
2707 .account_data
2708 .rooms
2709 .insert(room_id.to_owned(), stable_room_account_data_events);
2710 client
2711 .process_sliding_sync(
2712 &response,
2713 &RequestedRequiredStates::default(),
2714 &client.state_store_lock().lock().await,
2715 )
2716 .await
2717 .expect("Failed to process sync");
2718
2719 assert_matches!(
2721 room_info_notable_update_stream.recv().await,
2722 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2723 assert_eq!(received_room_id, room_id);
2724 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2725 }
2726 );
2727 assert!(room_info_notable_update_stream.is_empty());
2728
2729 response
2732 .extensions
2733 .account_data
2734 .rooms
2735 .insert(room_id.to_owned(), unstable_room_account_data_events);
2736 client
2737 .process_sliding_sync(
2738 &response,
2739 &RequestedRequiredStates::default(),
2740 &client.state_store_lock().lock().await,
2741 )
2742 .await
2743 .expect("Failed to process sync");
2744
2745 assert_matches!(
2747 room_info_notable_update_stream.recv().await,
2748 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2749 assert_eq!(received_room_id, room_id);
2750 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::NONE), "{received_reasons:?}");
2751 }
2752 );
2753 assert!(room_info_notable_update_stream.is_empty());
2754
2755 let stable_room_account_data_events = vec![
2758 Raw::from_json_string(
2759 json!({
2760 "type": "m.marked_unread",
2761 "event_id": "$3",
2762 "content": { "unread": true },
2763 "sender": client.session_meta().unwrap().user_id,
2764 "origin_server_ts": 12344445,
2765 })
2766 .to_string(),
2767 )
2768 .unwrap(),
2769 ];
2770 response
2771 .extensions
2772 .account_data
2773 .rooms
2774 .insert(room_id.to_owned(), stable_room_account_data_events);
2775 client
2776 .process_sliding_sync(
2777 &response,
2778 &RequestedRequiredStates::default(),
2779 &client.state_store_lock().lock().await,
2780 )
2781 .await
2782 .expect("Failed to process sync");
2783
2784 assert_matches!(
2786 room_info_notable_update_stream.recv().await,
2787 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2788 assert_eq!(received_room_id, room_id);
2789 assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2790 }
2791 );
2792 assert!(room_info_notable_update_stream.is_empty());
2793 }
2794
2795 #[async_test]
2796 async fn test_pinned_events_are_updated_on_sync() {
2797 let user_a_id = user_id!("@a:e.uk");
2798 let client = logged_in_base_client(Some(user_a_id)).await;
2799 let room_id = room_id!("!r:e.uk");
2800 let pinned_event_id = owned_event_id!("$an-id:e.uk");
2801
2802 let mut room_response = http::response::Room::new();
2804 set_room_joined(&mut room_response, user_a_id);
2805 let response = response_with_room(room_id, room_response);
2806 client
2807 .process_sliding_sync(
2808 &response,
2809 &RequestedRequiredStates::default(),
2810 &client.state_store_lock().lock().await,
2811 )
2812 .await
2813 .expect("Failed to process sync");
2814
2815 let room = client.get_room(room_id).unwrap();
2817 let pinned_event_ids = room.pinned_event_ids();
2818 assert_matches!(pinned_event_ids, None);
2819
2820 let mut room_response = http::response::Room::new();
2822 room_response.required_state.push(make_state_event(
2823 user_a_id,
2824 "",
2825 RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2826 None,
2827 ));
2828 let response = response_with_room(room_id, room_response);
2829 client
2830 .process_sliding_sync(
2831 &response,
2832 &RequestedRequiredStates::default(),
2833 &client.state_store_lock().lock().await,
2834 )
2835 .await
2836 .expect("Failed to process sync");
2837
2838 let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2839 assert_eq!(pinned_event_ids.len(), 1);
2840 assert_eq!(pinned_event_ids[0], pinned_event_id);
2841
2842 let mut room_response = http::response::Room::new();
2844 room_response.required_state.push(make_state_event(
2845 user_a_id,
2846 "",
2847 RoomPinnedEventsEventContent::new(Vec::new()),
2848 None,
2849 ));
2850 let response = response_with_room(room_id, room_response);
2851 client
2852 .process_sliding_sync(
2853 &response,
2854 &RequestedRequiredStates::default(),
2855 &client.state_store_lock().lock().await,
2856 )
2857 .await
2858 .expect("Failed to process sync");
2859 let pinned_event_ids = room.pinned_event_ids().unwrap();
2860 assert!(pinned_event_ids.is_empty());
2861 }
2862
2863 #[async_test]
2864 async fn test_dms_are_processed_in_any_sync_response() {
2865 let current_user_id = user_id!("@current:e.uk");
2866 let client = logged_in_base_client(Some(current_user_id)).await;
2867 let user_a_id = user_id!("@a:e.uk");
2868 let user_b_id = user_id!("@b:e.uk");
2869 let room_id_1 = room_id!("!r:e.uk");
2870 let room_id_2 = room_id!("!s:e.uk");
2871
2872 let mut room_response = http::response::Room::new();
2873 set_room_joined(&mut room_response, user_a_id);
2874 let mut response = response_with_room(room_id_1, room_response);
2875 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2876 BTreeMap::new();
2877 direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2878 direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2879 response
2880 .extensions
2881 .account_data
2882 .global
2883 .push(make_global_account_data_event(DirectEventContent(direct_content)));
2884 client
2885 .process_sliding_sync(
2886 &response,
2887 &RequestedRequiredStates::default(),
2888 &client.state_store_lock().lock().await,
2889 )
2890 .await
2891 .expect("Failed to process sync");
2892
2893 let room_1 = client.get_room(room_id_1).unwrap();
2894 assert!(room_1.is_direct().await.unwrap());
2895
2896 let mut room_response = http::response::Room::new();
2898 set_room_joined(&mut room_response, user_b_id);
2899 let response = response_with_room(room_id_2, room_response);
2900 client
2901 .process_sliding_sync(
2902 &response,
2903 &RequestedRequiredStates::default(),
2904 &client.state_store_lock().lock().await,
2905 )
2906 .await
2907 .expect("Failed to process sync");
2908
2909 let room_2 = client.get_room(room_id_2).unwrap();
2910 assert!(room_2.is_direct().await.unwrap());
2911 }
2912
2913 #[async_test]
2914 async fn test_room_encryption_state_is_and_is_not_encrypted() {
2915 let user_id = user_id!("@raclette:patate");
2916 let client = logged_in_base_client(Some(user_id)).await;
2917 let room_id_0 = room_id!("!r0");
2918 let room_id_1 = room_id!("!r1");
2919 let room_id_2 = room_id!("!r2");
2920
2921 let requested_required_states = RequestedRequiredStates::from(&{
2938 let mut request = http::Request::new();
2939
2940 request.room_subscriptions.insert(room_id_0.to_owned(), {
2941 let mut room_subscription = http::request::RoomSubscription::default();
2942
2943 room_subscription
2944 .required_state
2945 .push((StateEventType::RoomEncryption, "".to_owned()));
2946
2947 room_subscription
2948 });
2949
2950 request
2951 });
2952
2953 let mut response = http::Response::new("0".to_owned());
2954
2955 {
2960 let not_encrypted_room = http::response::Room::new();
2961 let mut encrypted_room = http::response::Room::new();
2962 set_room_is_encrypted(&mut encrypted_room, user_id);
2963
2964 response.rooms.insert(room_id_0.to_owned(), encrypted_room.clone());
2965 response.rooms.insert(room_id_1.to_owned(), encrypted_room);
2966 response.rooms.insert(room_id_2.to_owned(), not_encrypted_room);
2967 }
2968
2969 client
2970 .process_sliding_sync(
2971 &response,
2972 &requested_required_states,
2973 &client.state_store_lock().lock().await,
2974 )
2975 .await
2976 .expect("Failed to process sync");
2977
2978 assert_matches!(
2980 client.get_room(room_id_0).unwrap().encryption_state(),
2981 EncryptionState::Encrypted
2982 );
2983 assert_matches!(
2984 client.get_room(room_id_1).unwrap().encryption_state(),
2985 EncryptionState::Encrypted
2986 );
2987 assert_matches!(
2989 client.get_room(room_id_2).unwrap().encryption_state(),
2990 EncryptionState::NotEncrypted
2991 )
2992 }
2993
2994 #[async_test]
2995 async fn test_room_encryption_state_is_unknown() {
2996 let user_id = user_id!("@raclette:patate");
2997 let client = logged_in_base_client(Some(user_id)).await;
2998 let room_id_0 = room_id!("!r0");
2999 let room_id_1 = room_id!("!r1");
3000
3001 let requested_required_states = RequestedRequiredStates::from(&http::Request::new());
3014
3015 let mut response = http::Response::new("0".to_owned());
3016
3017 {
3019 let not_encrypted_room = http::response::Room::new();
3020 let mut encrypted_room = http::response::Room::new();
3021 set_room_is_encrypted(&mut encrypted_room, user_id);
3022
3023 response.rooms.insert(room_id_0.to_owned(), encrypted_room);
3024 response.rooms.insert(room_id_1.to_owned(), not_encrypted_room);
3025 }
3026
3027 client
3028 .process_sliding_sync(
3029 &response,
3030 &requested_required_states,
3031 &client.state_store_lock().lock().await,
3032 )
3033 .await
3034 .expect("Failed to process sync");
3035
3036 assert_matches!(
3039 client.get_room(room_id_0).unwrap().encryption_state(),
3040 EncryptionState::Encrypted
3041 );
3042 assert_matches!(
3045 client.get_room(room_id_1).unwrap().encryption_state(),
3046 EncryptionState::Unknown
3047 );
3048 }
3049
3050 async fn membership(
3051 client: &BaseClient,
3052 room_id: &RoomId,
3053 user_id: &UserId,
3054 ) -> MembershipState {
3055 let room = client.get_room(room_id).expect("Room not found!");
3056 let member = room.get_member(user_id).await.unwrap().expect("B not in room");
3057 member.membership().clone()
3058 }
3059
3060 fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
3061 let room = client.get_room(room_id).expect("Room not found!");
3062 room.direct_targets()
3063 }
3064
3065 async fn create_dm(
3068 client: &BaseClient,
3069 room_id: &RoomId,
3070 my_id: &UserId,
3071 their_id: &UserId,
3072 other_state: MembershipState,
3073 ) {
3074 let mut room = http::response::Room::new();
3075 set_room_joined(&mut room, my_id);
3076
3077 match other_state {
3078 MembershipState::Join => {
3079 room.joined_count = Some(uint!(2));
3080 room.invited_count = None;
3081 }
3082
3083 MembershipState::Invite => {
3084 room.joined_count = Some(uint!(1));
3085 room.invited_count = Some(uint!(1));
3086 }
3087
3088 _ => {
3089 room.joined_count = Some(uint!(1));
3090 room.invited_count = None;
3091 }
3092 }
3093
3094 room.required_state.push(make_membership_event(their_id, other_state));
3095
3096 let mut response = response_with_room(room_id, room);
3097 set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
3098 client
3099 .process_sliding_sync(
3100 &response,
3101 &RequestedRequiredStates::default(),
3102 &client.state_store_lock().lock().await,
3103 )
3104 .await
3105 .expect("Failed to process sync");
3106 }
3107
3108 async fn update_room_membership(
3110 client: &BaseClient,
3111 room_id: &RoomId,
3112 user_id: &UserId,
3113 new_state: MembershipState,
3114 ) {
3115 let mut room = http::response::Room::new();
3116 room.required_state.push(make_membership_event(user_id, new_state));
3117 let response = response_with_room(room_id, room);
3118 client
3119 .process_sliding_sync(
3120 &response,
3121 &RequestedRequiredStates::default(),
3122 &client.state_store_lock().lock().await,
3123 )
3124 .await
3125 .expect("Failed to process sync");
3126 }
3127
3128 fn set_direct_with(
3129 response: &mut http::Response,
3130 user_id: OwnedUserId,
3131 room_ids: Vec<OwnedRoomId>,
3132 ) {
3133 let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
3134 BTreeMap::new();
3135 direct_content.insert(user_id.into(), room_ids);
3136 response
3137 .extensions
3138 .account_data
3139 .global
3140 .push(make_global_account_data_event(DirectEventContent(direct_content)));
3141 }
3142
3143 fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
3144 let mut response = http::Response::new("5".to_owned());
3145 response.rooms.insert(room_id.to_owned(), room);
3146 response
3147 }
3148
3149 fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
3150 let mut room = http::response::Room::new();
3151
3152 let mut avatar_event_content = RoomAvatarEventContent::new();
3153 avatar_event_content.url = Some(avatar_uri.to_owned());
3154
3155 room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
3156
3157 room
3158 }
3159
3160 fn room_with_canonical_alias(
3161 room_alias_id: &RoomAliasId,
3162 user_id: &UserId,
3163 ) -> http::response::Room {
3164 let mut room = http::response::Room::new();
3165
3166 let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
3167 canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
3168
3169 room.required_state.push(make_state_event(
3170 user_id,
3171 "",
3172 canonical_alias_event_content,
3173 None,
3174 ));
3175
3176 room
3177 }
3178
3179 fn room_with_name(name: &str, user_id: &UserId) -> http::response::Room {
3180 let mut room = http::response::Room::new();
3181
3182 let name_event_content = RoomNameEventContent::new(name.to_owned());
3183
3184 room.required_state.push(make_state_event(user_id, "", name_event_content, None));
3185
3186 room
3187 }
3188
3189 fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
3190 room.required_state.push(make_state_event(
3191 sender,
3192 "",
3193 RoomNameEventContent::new(name),
3194 None,
3195 ));
3196 }
3197
3198 fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
3199 let evt = Raw::new(&json!({
3203 "type": "m.room.member",
3204 "sender": inviter,
3205 "content": {
3206 "is_direct": true,
3207 "membership": "invite",
3208 },
3209 "state_key": invitee,
3210 }))
3211 .expect("Failed to make raw event")
3212 .cast_unchecked();
3213
3214 room.invite_state = Some(vec![evt]);
3215
3216 room.required_state.push(make_state_event(
3219 inviter,
3220 invitee.as_str(),
3221 RoomMemberEventContent::new(MembershipState::Invite),
3222 None,
3223 ));
3224 }
3225
3226 fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
3227 let evt = Raw::new(&json!({
3231 "type": "m.room.member",
3232 "sender": knocker,
3233 "content": {
3234 "is_direct": true,
3235 "membership": "knock",
3236 },
3237 "state_key": knocker,
3238 }))
3239 .expect("Failed to make raw event")
3240 .cast_unchecked();
3241
3242 room.invite_state = Some(vec![evt]);
3243 }
3244
3245 fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
3246 room.required_state.push(make_membership_event(user_id, MembershipState::Join));
3247 }
3248
3249 fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
3250 room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
3251 }
3252
3253 fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
3254 room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
3255 }
3256
3257 fn set_room_is_encrypted(room: &mut http::response::Room, user_id: &UserId) {
3258 room.required_state.push(make_encryption_event(user_id));
3259 }
3260
3261 fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
3262 make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
3263 }
3264
3265 fn make_encryption_event<K>(user_id: &UserId) -> Raw<K> {
3266 make_state_event(user_id, "", RoomEncryptionEventContent::with_recommended_defaults(), None)
3267 }
3268
3269 fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
3270 Raw::new(&json!({
3271 "type": content.event_type(),
3272 "content": content,
3273 }))
3274 .expect("Failed to create account data event")
3275 .cast_unchecked()
3276 }
3277
3278 fn make_state_event<C: StateEventContent, E>(
3279 sender: &UserId,
3280 state_key: &str,
3281 content: C,
3282 prev_content: Option<C>,
3283 ) -> Raw<E> {
3284 let unsigned = if let Some(prev_content) = prev_content {
3285 json!({ "prev_content": prev_content })
3286 } else {
3287 json!({})
3288 };
3289
3290 Raw::new(&json!({
3291 "type": content.event_type(),
3292 "state_key": state_key,
3293 "content": content,
3294 "event_id": event_id!("$evt"),
3295 "sender": sender,
3296 "origin_server_ts": 10,
3297 "unsigned": unsigned,
3298 }))
3299 .expect("Failed to create state event")
3300 .cast_unchecked()
3301 }
3302
3303 fn make_profile_update(field: ProfileFieldName, value: serde_json::Value) -> UserProfileUpdate {
3304 let mut changes = UserProfileChanges::new();
3305 changes.updated.insert(field, value);
3306 UserProfileUpdate::Updated(changes)
3307 }
3308}