1#![doc = include_str!("README.md")]
17
18mod builder;
19mod cache;
20mod client;
21mod error;
22mod list;
23
24use std::{
25 collections::{BTreeMap, btree_map::Entry},
26 fmt::Debug,
27 future::Future,
28 sync::{Arc, RwLock as StdRwLock, RwLockWriteGuard as StdRwLockWriteGuard},
29 time::Duration,
30};
31
32use async_stream::stream;
33pub use client::{Version, VersionBuilder};
34use futures_core::stream::Stream;
35use matrix_sdk_base::RequestedRequiredStates;
36#[cfg(feature = "e2e-encryption")]
37use matrix_sdk_common::executor::JoinHandleExt as _;
38use matrix_sdk_common::{executor::spawn, timer};
39use ruma::{
40 OwnedRoomId, RoomId,
41 api::{client::sync::sync_events::v5 as http, error::ErrorKind},
42 assign,
43};
44use tokio::{
45 select,
46 sync::{Mutex as AsyncMutex, OwnedMutexGuard, RwLock as AsyncRwLock, broadcast::Sender},
47};
48use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
49
50pub use self::{builder::*, client::VersionBuilderError, error::*, list::*};
51use self::{cache::restore_sliding_sync_state, client::SlidingSyncResponseProcessor};
52use crate::{Client, Result, config::RequestConfig};
53
54#[derive(Clone, Debug)]
58pub struct SlidingSync {
59 inner: Arc<SlidingSyncInner>,
61}
62
63#[derive(Debug)]
64pub(super) struct SlidingSyncInner {
65 id: String,
69
70 client: Client,
72
73 poll_timeout: Duration,
75
76 network_timeout: Duration,
79
80 storage_key: String,
82
83 share_pos: bool,
90
91 position: Arc<AsyncMutex<SlidingSyncPositionMarkers>>,
104
105 lists: AsyncRwLock<BTreeMap<String, SlidingSyncList>>,
107
108 room_subscriptions: StdRwLock<BTreeMap<OwnedRoomId, http::request::RoomSubscription>>,
111
112 extensions: http::request::Extensions,
115
116 internal_channel: Sender<SlidingSyncInternalMessage>,
119}
120
121impl SlidingSync {
122 pub(super) fn new(inner: SlidingSyncInner) -> Self {
123 Self { inner: Arc::new(inner) }
124 }
125
126 async fn cache_to_storage(&self, position: &SlidingSyncPositionMarkers) -> Result<()> {
127 cache::store_sliding_sync_state(self, position).await
128 }
129
130 pub fn builder(id: String, client: Client) -> Result<SlidingSyncBuilder, Error> {
132 SlidingSyncBuilder::new(id, client)
133 }
134
135 pub fn subscribe_to_rooms(
142 &self,
143 room_ids: &[&RoomId],
144 settings: Option<http::request::RoomSubscription>,
145 cancel_in_flight_request: bool,
146 ) {
147 if subscribe_to_rooms(
148 self.inner.room_subscriptions.write().unwrap(),
149 &self.inner.client,
150 room_ids,
151 settings,
152 cancel_in_flight_request,
153 ) {
154 self.inner.internal_channel_send_if_possible(
155 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
156 );
157 }
158 }
159
160 pub fn unsubscribe_to_rooms(&self, room_ids: &[&RoomId], cancel_in_flight_request: bool) {
162 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
163 let mut skip_over_current_sync_loop_iteration = false;
164
165 for room_id in room_ids {
166 if room_subscriptions.remove(*room_id).is_some() {
167 skip_over_current_sync_loop_iteration = true;
168 }
169 }
170
171 if cancel_in_flight_request && skip_over_current_sync_loop_iteration {
172 self.inner.internal_channel_send_if_possible(
173 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
174 );
175 }
176 }
177
178 pub fn resubscribe_to_rooms(
196 &self,
197 room_ids: &[&RoomId],
198 settings: Option<http::request::RoomSubscription>,
199 cancel_in_flight_request: bool,
200 ) {
201 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
202 let mut skip_over_current_sync_loop_iteration = false;
203
204 room_subscriptions.retain(|room_id, _| {
206 if room_ids.contains(&room_id.as_ref()) {
207 true
208 } else {
209 skip_over_current_sync_loop_iteration = true;
210 false
211 }
212 });
213
214 if subscribe_to_rooms(
215 room_subscriptions,
216 &self.inner.client,
217 room_ids,
218 settings,
219 cancel_in_flight_request && skip_over_current_sync_loop_iteration,
220 ) {
221 self.inner.internal_channel_send_if_possible(
222 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
223 );
224 }
225 }
226
227 pub fn clear_and_subscribe_to_rooms(
232 &self,
233 room_ids: &[&RoomId],
234 settings: Option<http::request::RoomSubscription>,
235 cancel_in_flight_request: bool,
236 ) {
237 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
238 room_subscriptions.clear();
239
240 if subscribe_to_rooms(
241 room_subscriptions,
242 &self.inner.client,
243 room_ids,
244 settings,
245 cancel_in_flight_request,
246 ) {
247 self.inner.internal_channel_send_if_possible(
248 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
249 );
250 }
251 }
252
253 pub async fn on_list<Function, FunctionOutput, R>(
255 &self,
256 list_name: &str,
257 function: Function,
258 ) -> Option<R>
259 where
260 Function: FnOnce(&SlidingSyncList) -> FunctionOutput,
261 FunctionOutput: Future<Output = R>,
262 {
263 let lists = self.inner.lists.read().await;
264
265 match lists.get(list_name) {
266 Some(list) => Some(function(list).await),
267 None => None,
268 }
269 }
270
271 pub async fn add_list(
277 &self,
278 list_builder: SlidingSyncListBuilder,
279 ) -> Result<Option<SlidingSyncList>> {
280 let list = list_builder.build(self.inner.internal_channel.clone());
281
282 let old_list = self.inner.lists.write().await.insert(list.name().to_owned(), list);
283
284 self.inner.internal_channel_send_if_possible(
285 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
286 );
287
288 Ok(old_list)
289 }
290
291 pub async fn add_cached_list(
298 &self,
299 mut list_builder: SlidingSyncListBuilder,
300 ) -> Result<Option<SlidingSyncList>> {
301 let _timer = timer!(format!("restoring (loading+processing) list {}", list_builder.name));
302
303 list_builder.set_cached_and_reload(&self.inner.client, &self.inner.storage_key).await?;
304
305 self.add_list(list_builder).await
306 }
307
308 #[instrument(skip_all)]
310 async fn handle_response(
311 &self,
312 mut sliding_sync_response: http::Response,
313 position: &mut SlidingSyncPositionMarkers,
314 requested_required_states: RequestedRequiredStates,
315 ) -> Result<UpdateSummary, crate::Error> {
316 let pos = Some(sliding_sync_response.pos.clone());
317
318 let must_process_rooms_response = self.must_process_rooms_response().await;
319
320 trace!(yes = must_process_rooms_response, "Must process rooms response?");
321
322 let sync_response = {
330 let _timer = timer!("response processor");
331
332 let response_processor = {
333 let state_store_guard = {
336 let _timer = timer!("acquiring the `state_store_lock`");
337
338 self.inner.client.base_client().state_store_lock().lock().await
339 };
340
341 let mut response_processor =
342 SlidingSyncResponseProcessor::new(self.inner.client.clone());
343
344 if self.is_thread_subscriptions_enabled() {
350 response_processor
351 .handle_thread_subscriptions(
352 position.pos.as_deref(),
353 std::mem::take(
354 &mut sliding_sync_response.extensions.thread_subscriptions,
355 ),
356 )
357 .await?;
358 }
359
360 #[cfg(feature = "e2e-encryption")]
361 if self.is_e2ee_enabled() {
362 response_processor
363 .handle_encryption(&sliding_sync_response.extensions, &state_store_guard)
364 .await?
365 }
366
367 if must_process_rooms_response {
370 response_processor
371 .handle_room_response(
372 &sliding_sync_response,
373 &requested_required_states,
374 &state_store_guard,
375 )
376 .await?;
377 }
378
379 response_processor
380 };
381
382 response_processor.process_and_take_response().await?
384 };
385
386 debug!("Sliding Sync response has been handled by the client");
387 trace!(?sync_response);
388
389 let update_summary = {
390 let updated_rooms = {
392 let mut updated_rooms = Vec::with_capacity(
393 sliding_sync_response.rooms.len() + sync_response.rooms.joined.len(),
394 );
395
396 updated_rooms.extend(sliding_sync_response.rooms.keys().cloned());
397
398 updated_rooms.extend(sync_response.rooms.joined.keys().cloned());
406
407 updated_rooms
408 };
409
410 let updated_lists = {
412 debug!(
413 lists = ?sliding_sync_response.lists,
414 "Update lists"
415 );
416
417 let mut updated_lists = Vec::with_capacity(sliding_sync_response.lists.len());
418 let mut lists = self.inner.lists.write().await;
419
420 for (name, list) in lists.iter_mut() {
423 if let Some(updates) = sliding_sync_response.lists.get(name) {
424 let maximum_number_of_rooms: u32 =
425 updates.count.try_into().expect("failed to convert `count` to `u32`");
426
427 if list.update(Some(maximum_number_of_rooms))? {
428 updated_lists.push(name.clone());
429 }
430 } else if list.update(None)? {
431 updated_lists.push(name.clone());
432 }
433 }
434
435 for name in sliding_sync_response.lists.keys() {
437 if !lists.contains_key(name) {
438 error!("Response for list `{name}` - unknown to us; skipping");
439 }
440 }
441
442 updated_lists
443 };
444
445 UpdateSummary { lists: updated_lists, rooms: updated_rooms }
446 };
447
448 debug!(previous_pos = position.pos, new_pos = pos, "Updating `pos`");
452
453 position.pos = pos;
454
455 Ok(update_summary)
456 }
457
458 async fn generate_sync_request(
459 &self,
460 ) -> Result<(http::Request, RequestConfig, OwnedMutexGuard<SlidingSyncPositionMarkers>)> {
461 let mut requests_lists = BTreeMap::new();
463
464 let timeout = {
465 let lists = self.inner.lists.read().await;
466
467 let mut timeout = PollTimeout::Default;
469
470 for (name, list) in lists.iter() {
471 requests_lists.insert(name.clone(), list.next_request()?);
472 timeout = timeout.min(list.requires_timeout());
473 }
474
475 timeout
476 };
477
478 let mut position_guard = {
486 debug!("Waiting to acquire the `position` lock");
487
488 let _timer = timer!("acquiring the `position` lock");
489
490 self.inner.position.clone().lock_owned().await
491 };
492
493 debug!(pos = ?position_guard.pos, "Got a position");
494
495 let to_device_enabled = self.inner.extensions.to_device.enabled == Some(true);
496
497 let restored_fields = if self.inner.share_pos || to_device_enabled {
498 restore_sliding_sync_state(&self.inner.client, &self.inner.storage_key).await?
499 } else {
500 None
501 };
502
503 let pos = if self.inner.share_pos {
506 if let Some(fields) = &restored_fields {
507 if fields.pos != position_guard.pos {
509 info!(
510 "Pos from previous request ('{:?}') was different from \
511 pos in database ('{:?}').",
512 position_guard.pos, fields.pos
513 );
514 position_guard.pos = fields.pos.clone();
515 }
516 fields.pos.clone()
517 } else {
518 position_guard.pos.clone()
519 }
520 } else {
521 position_guard.pos.clone()
522 };
523
524 #[cfg(feature = "e2e-encryption")]
533 if pos.is_none() && self.is_e2ee_enabled() {
534 info!("Marking all tracked users as dirty");
535
536 let olm_machine = self.inner.client.olm_machine().await;
537 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
538 olm_machine.mark_all_tracked_users_as_dirty().await?;
539 }
540
541 let timeout = match timeout {
546 PollTimeout::None => None,
547 PollTimeout::Some(timeout) => Some(Duration::from_secs(timeout.into())),
548 PollTimeout::Default => Some(self.inner.poll_timeout),
549 };
550
551 Span::current()
552 .record("pos", &pos)
553 .record("timeout", timeout.map(|duration| duration.as_millis()));
554
555 let mut request = assign!(http::Request::new(), {
556 conn_id: Some(self.inner.id.clone()),
557 pos,
558 set_presence: self.inner.client.sync_presence(),
559 timeout,
560 lists: requests_lists,
561 });
562
563 request.room_subscriptions = self.inner.room_subscriptions.read().unwrap().clone();
565
566 request.extensions = self.inner.extensions.clone();
568
569 if to_device_enabled {
571 request.extensions.to_device.since =
572 restored_fields.and_then(|fields| fields.to_device_token);
573 }
574
575 Ok((
576 request,
578 RequestConfig::default()
581 .timeout(self.inner.poll_timeout + self.inner.network_timeout)
582 .retry_limit(3),
583 position_guard,
584 ))
585 }
586
587 async fn send_sync_request(
591 &self,
592 request: http::Request,
593 request_config: RequestConfig,
594 mut position_guard: OwnedMutexGuard<SlidingSyncPositionMarkers>,
595 ) -> Result<UpdateSummary> {
596 debug!("Sending request");
597
598 let requested_required_states = RequestedRequiredStates::from(&request);
600 let request = self.inner.client.send(request).with_request_config(request_config);
601
602 #[cfg(feature = "e2e-encryption")]
609 let response = {
610 if self.is_e2ee_enabled() {
611 let client = self.inner.client.clone();
628 let e2ee_uploads = spawn(
629 async move {
630 if let Err(error) = client.send_outgoing_requests().await {
631 error!(?error, "Error while sending outgoing E2EE requests");
632 }
633 }
634 .instrument(Span::current()),
635 )
636 .abort_on_drop();
639
640 let response = request.await?;
642
643 e2ee_uploads.await.map_err(|error| Error::JoinError {
648 task_description: "e2ee_uploads".to_owned(),
649 error,
650 })?;
651
652 response
653 } else {
654 request.await?
655 }
656 };
657
658 #[cfg(not(feature = "e2e-encryption"))]
660 let response = request.await?;
661
662 debug!("Received response");
663
664 let this = self.clone();
674
675 let future = async move {
678 debug!("Start handling response");
679
680 let updates = this
686 .handle_response(response, &mut position_guard, requested_required_states)
687 .await?;
688
689 this.cache_to_storage(&position_guard).await?;
690
691 drop(position_guard);
694
695 debug!("Done handling response");
696
697 Ok(updates)
698 };
699
700 spawn(future.instrument(Span::current())).await.map_err(|error| Error::JoinError {
701 task_description: "handle_response".to_owned(),
702 error,
703 })?
704 }
705
706 #[cfg(feature = "e2e-encryption")]
708 fn is_e2ee_enabled(&self) -> bool {
709 self.inner.extensions.e2ee.enabled == Some(true)
710 }
711
712 fn is_thread_subscriptions_enabled(&self) -> bool {
715 self.inner.extensions.thread_subscriptions.enabled == Some(true)
716 }
717
718 #[cfg(not(feature = "e2e-encryption"))]
719 fn is_e2ee_enabled(&self) -> bool {
720 false
721 }
722
723 async fn must_process_rooms_response(&self) -> bool {
725 !self.inner.room_subscriptions.read().unwrap().is_empty()
728 || !self.inner.lists.read().await.is_empty()
729 }
730
731 #[doc(hidden)]
735 #[instrument(skip_all, fields(conn_id = self.inner.id, pos, timeout))]
736 pub async fn sync_once(&self) -> Result<UpdateSummary> {
737 let (request, request_config, position_guard) = self.generate_sync_request().await?;
738
739 let summaries = self.send_sync_request(request, request_config, position_guard).await?;
741
742 self.inner.client.inner.sync_beat.notify(usize::MAX);
744
745 Ok(summaries)
746 }
747
748 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(name = "sync_stream", skip_all, fields(conn_id = self.inner.id, with_e2ee = self.is_e2ee_enabled()))]
758 pub fn sync(&self) -> impl Stream<Item = Result<UpdateSummary, crate::Error>> + '_ {
759 debug!("Starting sync stream");
760
761 let mut internal_channel_receiver = self.inner.internal_channel.subscribe();
762
763 stream! {
764 loop {
765 debug!("Sync stream is running");
766
767 select! {
768 biased;
769
770 internal_message = internal_channel_receiver.recv() => {
771 use SlidingSyncInternalMessage::*;
772
773 debug!(?internal_message, "Sync stream has received an internal message");
774
775 match internal_message {
776 Err(_) | Ok(SyncLoopStop) => {
777 break;
778 }
779
780 Ok(SyncLoopSkipOverCurrentIteration) => {
781 continue;
782 }
783 }
784 }
785
786 update_summary = self.sync_once() => {
787 match update_summary {
788 Ok(updates) => {
789 yield Ok(updates);
790 }
791
792 Err(error) => {
794 if error.client_api_error_kind() == Some(&ErrorKind::UnknownPos) {
795 self.expire_session().await;
797 }
798
799 yield Err(error);
800
801 break;
803 }
804 }
805 }
806 }
807 }
808
809 debug!("Sync stream has exited.");
810 }
811 }
812
813 pub fn stop_sync(&self) -> Result<()> {
822 Ok(self.inner.internal_channel_send(SlidingSyncInternalMessage::SyncLoopStop)?)
823 }
824
825 #[doc(hidden)]
836 pub async fn expire_session(&self) {
837 info!("Session expired; resetting `pos`");
838
839 {
840 let lists = self.inner.lists.read().await;
841
842 for list in lists.values() {
843 list.set_maximum_number_of_rooms(None);
845 }
846 }
847
848 {
850 let mut position = self.inner.position.lock().await;
851
852 position.pos = None;
854
855 if let Err(err) = self.cache_to_storage(&position).await {
859 warn!("Failed to invalidate cached sliding sync state: {err}");
860 }
861 }
862
863 {
864 self.inner.room_subscriptions.write().unwrap().clear();
867 }
868 }
869}
870
871fn subscribe_to_rooms(
874 mut room_subscriptions: StdRwLockWriteGuard<
875 '_,
876 BTreeMap<OwnedRoomId, http::request::RoomSubscription>,
877 >,
878 client: &Client,
879 room_ids: &[&RoomId],
880 settings: Option<http::request::RoomSubscription>,
881 cancel_in_flight_request: bool,
882) -> bool {
883 let settings = settings.unwrap_or_default();
884 let mut skip_over_current_sync_loop_iteration = false;
885
886 for room_id in room_ids {
887 if let Entry::Vacant(entry) = room_subscriptions.entry((*room_id).to_owned()) {
888 if let Some(room) = client.get_room(room_id) {
889 room.mark_members_missing();
890 }
891
892 entry.insert(settings.clone());
893
894 skip_over_current_sync_loop_iteration = true;
895 }
896 }
897
898 cancel_in_flight_request && skip_over_current_sync_loop_iteration
899}
900
901impl SlidingSyncInner {
902 #[instrument]
904 fn internal_channel_send(&self, message: SlidingSyncInternalMessage) -> Result<(), Error> {
905 self.internal_channel.send(message).map(|_| ()).map_err(|_| Error::InternalChannelIsBroken)
906 }
907
908 #[instrument]
911 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
912 let _ = self.internal_channel.send(message);
914 }
915}
916
917#[derive(Copy, Clone, Debug, PartialEq)]
918enum SlidingSyncInternalMessage {
919 SyncLoopStop,
921
922 SyncLoopSkipOverCurrentIteration,
925}
926
927#[cfg(any(test, feature = "testing"))]
928impl SlidingSync {
929 pub async fn set_pos(&self, new_pos: String) {
931 let mut position_lock = self.inner.position.lock().await;
932 position_lock.pos = Some(new_pos);
933 }
934}
935
936#[derive(Clone, Debug)]
937pub(super) struct SlidingSyncPositionMarkers {
938 pos: Option<String>,
941}
942
943#[derive(Debug, Clone)]
946pub struct UpdateSummary {
947 pub lists: Vec<String>,
949 pub rooms: Vec<OwnedRoomId>,
951}
952
953#[derive(Debug)]
962pub enum PollTimeout {
963 None,
965
966 Some(u32),
969
970 Default,
973}
974
975impl PollTimeout {
976 fn min(self, left: Self) -> Self {
987 match (self, left) {
988 (Self::None, _) => Self::None,
989
990 (Self::Some(_), Self::None) => Self::None,
991 (Self::Some(right), Self::Some(left)) => Self::Some(right.min(left)),
992 (Self::Some(right), Self::Default) => Self::Some(right),
993
994 (Self::Default, Self::None) => Self::None,
995 (Self::Default, Self::Some(left)) => Self::Some(left),
996 (Self::Default, Self::Default) => Self::Default,
997 }
998 }
999}
1000
1001#[cfg(all(test, not(target_family = "wasm")))]
1002#[allow(clippy::dbg_macro)]
1003mod tests {
1004 use std::{
1005 collections::BTreeMap,
1006 future::ready,
1007 ops::Not,
1008 sync::{Arc, Mutex},
1009 time::Duration,
1010 };
1011
1012 use assert_matches::assert_matches;
1013 use event_listener::Listener;
1014 use futures_util::{StreamExt, future::join_all, pin_mut};
1015 use matrix_sdk_base::{RequestedRequiredStates, RoomMemberships};
1016 use matrix_sdk_common::executor::spawn;
1017 use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
1018 use ruma::{
1019 OwnedRoomId, assign,
1020 events::{direct::DirectEvent, room::member::MembershipState},
1021 owned_room_id,
1022 presence::PresenceState,
1023 room_id,
1024 serde::Raw,
1025 uint,
1026 };
1027 use serde::Deserialize;
1028 use serde_json::json;
1029 use wiremock::{
1030 Match, Mock, MockServer, Request, ResponseTemplate, http::Method, matchers::method,
1031 };
1032
1033 use super::{
1034 SlidingSync, SlidingSyncBuilder, SlidingSyncList, SlidingSyncListBuilder, SlidingSyncMode,
1035 cache::restore_sliding_sync_state, http,
1036 };
1037 use crate::{
1038 Client, Result,
1039 test_utils::{logged_in_client, mocks::MatrixMockServer},
1040 };
1041
1042 #[derive(Copy, Clone)]
1043 struct SlidingSyncMatcher;
1044
1045 impl Match for SlidingSyncMatcher {
1046 fn matches(&self, request: &Request) -> bool {
1047 request.url.path() == "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"
1048 && request.method == Method::POST
1049 }
1050 }
1051
1052 async fn new_sliding_sync(
1053 lists: Vec<SlidingSyncListBuilder>,
1054 ) -> Result<(MockServer, SlidingSync)> {
1055 let server = MockServer::start().await;
1056 let client = logged_in_client(Some(server.uri())).await;
1057
1058 let mut sliding_sync_builder = client.sliding_sync("test-slidingsync")?;
1059
1060 for list in lists {
1061 sliding_sync_builder = sliding_sync_builder.add_list(list);
1062 }
1063
1064 let sliding_sync = sliding_sync_builder.build().await?;
1065
1066 Ok((server, sliding_sync))
1067 }
1068
1069 #[async_test]
1070 async fn test_sliding_sync_request_uses_client_sync_presence() -> Result<()> {
1071 let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
1072 let client = sliding_sync.inner.client.clone();
1073
1074 {
1075 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1076
1077 assert_eq!(request.set_presence, PresenceState::Online);
1078 }
1079
1080 client.set_presence(PresenceState::Unavailable, None, false).await?;
1081
1082 {
1083 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1084
1085 assert_eq!(request.set_presence, PresenceState::Unavailable);
1086 }
1087
1088 client.set_presence(PresenceState::Offline, None, false).await?;
1089
1090 {
1091 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1092
1093 assert_eq!(request.set_presence, PresenceState::Offline);
1094 }
1095
1096 Ok(())
1097 }
1098
1099 #[async_test]
1100 async fn test_subscribe_to_rooms() -> Result<()> {
1101 let (server, sliding_sync) = new_sliding_sync(vec![
1102 SlidingSyncList::builder("foo")
1103 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1104 ])
1105 .await?;
1106
1107 let stream = sliding_sync.sync();
1108 pin_mut!(stream);
1109
1110 let room_id_0 = room_id!("!r0:bar.org");
1111 let room_id_1 = room_id!("!r1:bar.org");
1112 let room_id_2 = room_id!("!r2:bar.org");
1113
1114 {
1115 let _mock_guard = Mock::given(SlidingSyncMatcher)
1116 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1117 "pos": "1",
1118 "lists": {},
1119 "rooms": {
1120 room_id_0: {
1121 "name": "Room #0",
1122 "initial": true,
1123 },
1124 room_id_1: {
1125 "name": "Room #1",
1126 "initial": true,
1127 },
1128 room_id_2: {
1129 "name": "Room #2",
1130 "initial": true,
1131 },
1132 }
1133 })))
1134 .mount_as_scoped(&server)
1135 .await;
1136
1137 let _ = stream.next().await.unwrap()?;
1138 }
1139
1140 let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1141
1142 assert!(room0.are_members_synced().not());
1146
1147 {
1148 struct MemberMatcher(OwnedRoomId);
1149
1150 impl Match for MemberMatcher {
1151 fn matches(&self, request: &Request) -> bool {
1152 request.url.path()
1153 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1154 && request.method == Method::GET
1155 }
1156 }
1157
1158 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1159 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1160 "chunk": [],
1161 })))
1162 .mount_as_scoped(&server)
1163 .await;
1164
1165 assert_matches!(room0.request_members().await, Ok(()));
1166 }
1167
1168 assert!(room0.are_members_synced());
1170
1171 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], None, true);
1172
1173 assert!(room0.are_members_synced().not());
1176
1177 {
1178 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1179
1180 assert!(room_subscriptions.contains_key(room_id_0));
1181 assert!(room_subscriptions.contains_key(room_id_1));
1182 assert!(!room_subscriptions.contains_key(room_id_2));
1183 }
1184
1185 {
1188 struct MemberMatcher(OwnedRoomId);
1189
1190 impl Match for MemberMatcher {
1191 fn matches(&self, request: &Request) -> bool {
1192 request.url.path()
1193 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1194 && request.method == Method::GET
1195 }
1196 }
1197
1198 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1199 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1200 "chunk": [],
1201 })))
1202 .mount_as_scoped(&server)
1203 .await;
1204
1205 assert_matches!(room0.request_members().await, Ok(()));
1206 }
1207
1208 assert!(room0.are_members_synced());
1210
1211 sliding_sync.subscribe_to_rooms(&[room_id_0], None, false);
1212
1213 assert!(room0.are_members_synced());
1216
1217 Ok(())
1218 }
1219
1220 #[async_test]
1221 async fn test_subscribe_unsubscribe_and_clear_and_subscribe_to_rooms() -> Result<()> {
1222 let (_server, sliding_sync) = new_sliding_sync(vec![
1223 SlidingSyncList::builder("foo")
1224 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1225 ])
1226 .await?;
1227
1228 let room_id_0 = room_id!("!r0:bar.org");
1229 let room_id_1 = room_id!("!r1:bar.org");
1230 let room_id_2 = room_id!("!r2:bar.org");
1231 let room_id_3 = room_id!("!r3:bar.org");
1232
1233 {
1235 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1236
1237 assert!(room_subscriptions.is_empty());
1238 }
1239
1240 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], Default::default(), false);
1242
1243 {
1244 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1245
1246 assert_eq!(room_subscriptions.len(), 2);
1247 assert!(room_subscriptions.contains_key(room_id_0));
1248 assert!(room_subscriptions.contains_key(room_id_1));
1249 }
1250
1251 sliding_sync.unsubscribe_to_rooms(&[room_id_0], false);
1253
1254 {
1255 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1256
1257 assert_eq!(room_subscriptions.len(), 1);
1258 assert!(room_subscriptions.contains_key(room_id_1));
1259 }
1260
1261 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], Default::default(), false);
1263
1264 {
1265 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1266
1267 assert_eq!(room_subscriptions.len(), 2);
1268 assert!(room_subscriptions.contains_key(room_id_0));
1269 assert!(room_subscriptions.contains_key(room_id_1));
1270 }
1271
1272 sliding_sync.clear_and_subscribe_to_rooms(
1274 &[room_id_2, room_id_3],
1275 Default::default(),
1276 false,
1277 );
1278
1279 {
1280 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1281
1282 assert_eq!(room_subscriptions.len(), 2);
1283 assert!(room_subscriptions.contains_key(room_id_2));
1284 assert!(room_subscriptions.contains_key(room_id_3));
1285 }
1286
1287 Ok(())
1288 }
1289
1290 #[async_test]
1291 async fn test_resubscribe_to_rooms() -> Result<()> {
1292 let (server, sliding_sync) = new_sliding_sync(vec![
1293 SlidingSyncList::builder("foo")
1294 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1295 ])
1296 .await?;
1297
1298 let stream = sliding_sync.sync();
1299 pin_mut!(stream);
1300
1301 let room_id_0 = room_id!("!r0:bar.org");
1302 let room_id_1 = room_id!("!r1:bar.org");
1303 let room_id_2 = room_id!("!r2:bar.org");
1304
1305 {
1306 let _mock_guard = Mock::given(SlidingSyncMatcher)
1307 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1308 "pos": "1",
1309 "lists": {},
1310 "rooms": {
1311 room_id_0: {
1312 "name": "Room #0",
1313 "initial": true,
1314 },
1315 room_id_1: {
1316 "name": "Room #1",
1317 "initial": true,
1318 },
1319 room_id_2: {
1320 "name": "Room #2",
1321 "initial": true,
1322 },
1323 }
1324 })))
1325 .mount_as_scoped(&server)
1326 .await;
1327
1328 let _ = stream.next().await.unwrap()?;
1329 }
1330
1331 let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1332
1333 assert!(room0.are_members_synced().not());
1337
1338 {
1339 struct MemberMatcher(OwnedRoomId);
1340
1341 impl Match for MemberMatcher {
1342 fn matches(&self, request: &Request) -> bool {
1343 request.url.path()
1344 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1345 && request.method == Method::GET
1346 }
1347 }
1348
1349 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1350 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1351 "chunk": [],
1352 })))
1353 .mount_as_scoped(&server)
1354 .await;
1355
1356 assert_matches!(room0.request_members().await, Ok(()));
1357 }
1358
1359 assert!(room0.are_members_synced());
1361
1362 sliding_sync.resubscribe_to_rooms(&[room_id_0, room_id_1], None, true);
1363
1364 assert!(room0.are_members_synced().not());
1367
1368 {
1370 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1371
1372 assert!(room_subscriptions.contains_key(room_id_0));
1373 assert!(room_subscriptions.contains_key(room_id_1));
1374 assert!(!room_subscriptions.contains_key(room_id_2));
1375 }
1376
1377 {
1380 struct MemberMatcher(OwnedRoomId);
1381
1382 impl Match for MemberMatcher {
1383 fn matches(&self, request: &Request) -> bool {
1384 request.url.path()
1385 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1386 && request.method == Method::GET
1387 }
1388 }
1389
1390 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1391 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1392 "chunk": [],
1393 })))
1394 .mount_as_scoped(&server)
1395 .await;
1396
1397 assert_matches!(room0.request_members().await, Ok(()));
1398 }
1399
1400 assert!(room0.are_members_synced());
1402
1403 sliding_sync.resubscribe_to_rooms(&[room_id_0], None, false);
1404
1405 assert!(room0.are_members_synced());
1408
1409 {
1411 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1412
1413 assert!(room_subscriptions.contains_key(room_id_0));
1414 assert!(!room_subscriptions.contains_key(room_id_1));
1415 assert!(!room_subscriptions.contains_key(room_id_2));
1416 }
1417 Ok(())
1418 }
1419
1420 #[async_test]
1421 async fn test_room_subscriptions_are_reset_when_session_expires() -> Result<()> {
1422 let (_server, sliding_sync) = new_sliding_sync(vec![
1423 SlidingSyncList::builder("foo")
1424 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1425 ])
1426 .await?;
1427
1428 let room_id_0 = room_id!("!r0:bar.org");
1429 let room_id_1 = room_id!("!r1:bar.org");
1430 let room_id_2 = room_id!("!r2:bar.org");
1431
1432 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], None, false);
1434
1435 {
1436 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1437
1438 assert!(room_subscriptions.contains_key(room_id_0));
1439 assert!(room_subscriptions.contains_key(room_id_1));
1440 assert!(room_subscriptions.contains_key(room_id_2).not());
1441 }
1442
1443 sliding_sync.subscribe_to_rooms(&[room_id_2], None, false);
1445
1446 {
1447 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1448
1449 assert!(room_subscriptions.contains_key(room_id_0));
1450 assert!(room_subscriptions.contains_key(room_id_1));
1451 assert!(room_subscriptions.contains_key(room_id_2));
1452 }
1453
1454 sliding_sync.expire_session().await;
1456
1457 {
1458 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1459
1460 assert!(room_subscriptions.is_empty());
1461 }
1462
1463 sliding_sync.subscribe_to_rooms(&[room_id_2], None, false);
1465
1466 {
1467 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1468
1469 assert!(room_subscriptions.contains_key(room_id_0).not());
1470 assert!(room_subscriptions.contains_key(room_id_1).not());
1471 assert!(room_subscriptions.contains_key(room_id_2));
1472 }
1473
1474 Ok(())
1475 }
1476
1477 #[async_test]
1478 async fn test_add_list() -> Result<()> {
1479 let (_server, sliding_sync) = new_sliding_sync(vec![
1480 SlidingSyncList::builder("foo")
1481 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1482 ])
1483 .await?;
1484
1485 let _stream = sliding_sync.sync();
1486 pin_mut!(_stream);
1487
1488 sliding_sync
1489 .add_list(
1490 SlidingSyncList::builder("bar")
1491 .sync_mode(SlidingSyncMode::new_selective().add_range(50..=60)),
1492 )
1493 .await?;
1494
1495 let lists = sliding_sync.inner.lists.read().await;
1496
1497 assert!(lists.contains_key("foo"));
1498 assert!(lists.contains_key("bar"));
1499
1500 Ok(())
1503 }
1504
1505 #[cfg(feature = "e2e-encryption")]
1506 #[async_test]
1507 async fn test_extensions_to_device_since_is_set() {
1508 use matrix_sdk_base::crypto::store::types::Changes;
1509
1510 let client = logged_in_client(None).await;
1511 let sliding_sync = SlidingSyncBuilder::new("foo".to_owned(), client.clone())
1512 .unwrap()
1513 .with_to_device_extension(assign!(
1514 http::request::ToDevice::default(),
1515 {
1516 enabled: Some(true),
1517 }
1518 ))
1519 .build()
1520 .await
1521 .unwrap();
1522
1523 {
1525 let to_device = &sliding_sync.inner.extensions.to_device;
1526
1527 assert_eq!(to_device.enabled, Some(true));
1528 assert!(to_device.since.is_none());
1529 }
1530
1531 {
1533 let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1534
1535 let to_device = &request.extensions.to_device;
1536
1537 assert_eq!(to_device.enabled, Some(true));
1538 assert!(to_device.since.is_none());
1539 }
1540
1541 let since_token = "depuis".to_owned();
1543
1544 {
1545 if let Some(olm_machine) = &*client.olm_machine().await {
1546 olm_machine
1547 .store()
1548 .save_changes(Changes {
1549 next_batch_token: Some(since_token.clone()),
1550 ..Default::default()
1551 })
1552 .await
1553 .unwrap();
1554 } else {
1555 panic!("Where is the Olm machine?");
1556 }
1557 }
1558
1559 {
1561 let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1562
1563 let to_device = &request.extensions.to_device;
1564
1565 assert_eq!(to_device.enabled, Some(true));
1566 assert_eq!(to_device.since, Some(since_token));
1567 }
1568 }
1569
1570 #[async_test]
1576 #[cfg(feature = "e2e-encryption")]
1577 async fn test_no_pos_with_e2ee_marks_all_tracked_users_as_dirty() -> anyhow::Result<()> {
1578 use matrix_sdk_base::crypto::types::requests::{AnyIncomingResponse, AnyOutgoingRequest};
1579 use matrix_sdk_test::ruma_response_from_json;
1580 use ruma::user_id;
1581
1582 let server = MockServer::start().await;
1583 let client = logged_in_client(Some(server.uri())).await;
1584
1585 let alice = user_id!("@alice:localhost");
1586 let bob = user_id!("@bob:localhost");
1587 let me = user_id!("@example:localhost");
1588
1589 {
1592 let olm_machine = client.olm_machine().await;
1593 let olm_machine = olm_machine.as_ref().unwrap();
1594
1595 olm_machine.update_tracked_users([alice, bob]).await?;
1596
1597 let outgoing_requests = olm_machine.outgoing_requests().await?;
1599
1600 assert_eq!(outgoing_requests.len(), 2);
1601 assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysUpload(_));
1602 assert_matches!(outgoing_requests[1].request(), AnyOutgoingRequest::KeysQuery(_));
1603
1604 olm_machine
1606 .mark_request_as_sent(
1607 outgoing_requests[0].request_id(),
1608 AnyIncomingResponse::KeysUpload(&ruma_response_from_json(&json!({
1609 "one_time_key_counts": {}
1610 }))),
1611 )
1612 .await?;
1613
1614 olm_machine
1615 .mark_request_as_sent(
1616 outgoing_requests[1].request_id(),
1617 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1618 "device_keys": {
1619 alice: {},
1620 bob: {},
1621 }
1622 }))),
1623 )
1624 .await?;
1625
1626 let outgoing_requests = olm_machine.outgoing_requests().await?;
1628
1629 assert_eq!(outgoing_requests.len(), 1);
1630 assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysQuery(_));
1631
1632 olm_machine
1633 .mark_request_as_sent(
1634 outgoing_requests[0].request_id(),
1635 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1636 "device_keys": {
1637 me: {},
1638 }
1639 }))),
1640 )
1641 .await?;
1642
1643 let outgoing_requests = olm_machine.outgoing_requests().await?;
1645
1646 assert!(outgoing_requests.is_empty());
1647 }
1648
1649 let sync = client
1650 .sliding_sync("test-slidingsync")?
1651 .add_list(SlidingSyncList::builder("new_list"))
1652 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
1653 .build()
1654 .await?;
1655
1656 let (_request, _, _) = sync.generate_sync_request().await?;
1658
1659 {
1661 let olm_machine = client.olm_machine().await;
1662 let olm_machine = olm_machine.as_ref().unwrap();
1663
1664 let outgoing_requests = olm_machine.outgoing_requests().await?;
1666
1667 assert_eq!(outgoing_requests.len(), 1);
1668 assert_matches!(
1669 outgoing_requests[0].request(),
1670 AnyOutgoingRequest::KeysQuery(request) => {
1671 assert!(request.device_keys.contains_key(alice));
1672 assert!(request.device_keys.contains_key(bob));
1673 assert!(request.device_keys.contains_key(me));
1674 }
1675 );
1676
1677 olm_machine
1679 .mark_request_as_sent(
1680 outgoing_requests[0].request_id(),
1681 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1682 "device_keys": {
1683 alice: {},
1684 bob: {},
1685 me: {},
1686 }
1687 }))),
1688 )
1689 .await?;
1690 }
1691
1692 sync.set_pos("chocolat".to_owned()).await;
1694
1695 let (_request, _, _) = sync.generate_sync_request().await?;
1696
1697 {
1699 let olm_machine = client.olm_machine().await;
1700 let olm_machine = olm_machine.as_ref().unwrap();
1701
1702 let outgoing_requests = olm_machine.outgoing_requests().await?;
1704
1705 assert!(outgoing_requests.is_empty());
1706 }
1707
1708 Ok(())
1709 }
1710
1711 #[cfg(feature = "e2e-encryption")]
1712 #[async_test]
1713 async fn test_sliding_sync_doesnt_remember_pos() -> Result<()> {
1714 let server = MockServer::start().await;
1715
1716 #[derive(Deserialize)]
1717 struct PartialRequest {
1718 txn_id: Option<String>,
1719 }
1720
1721 let server_pos = Arc::new(Mutex::new(0));
1722 let _mock_guard = Mock::given(SlidingSyncMatcher)
1723 .respond_with(move |request: &Request| {
1724 let request: PartialRequest = request.body_json().unwrap();
1726 let pos = {
1727 let mut pos = server_pos.lock().unwrap();
1728 let prev = *pos;
1729 *pos += 1;
1730 prev
1731 };
1732
1733 ResponseTemplate::new(200).set_body_json(json!({
1734 "txn_id": request.txn_id,
1735 "pos": pos.to_string(),
1736 }))
1737 })
1738 .mount_as_scoped(&server)
1739 .await;
1740
1741 let client = logged_in_client(Some(server.uri())).await;
1742
1743 let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1744
1745 {
1747 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1748
1749 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1750 assert!(request.pos.is_none());
1751 }
1752
1753 let sync = sliding_sync.sync();
1754 pin_mut!(sync);
1755
1756 let next = sync.next().await;
1759 assert_matches!(next, Some(Ok(_update_summary)));
1760
1761 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
1762
1763 let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
1764 .await?
1765 .expect("must have restored fields");
1766
1767 assert_eq!(restored_fields.pos.as_deref(), Some("0"));
1770
1771 {
1775 let other_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1776
1777 let mut position_guard = other_sync.inner.position.lock().await;
1778 position_guard.pos = Some("yolo".to_owned());
1779
1780 other_sync.cache_to_storage(&position_guard).await?;
1781 }
1782
1783 {
1785 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
1786 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1787 assert_eq!(request.pos.as_deref(), Some("0"));
1788 }
1789
1790 {
1793 let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1794 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1795 }
1796
1797 Ok(())
1798 }
1799
1800 #[cfg(feature = "e2e-encryption")]
1801 #[async_test]
1802 async fn test_sliding_sync_does_remember_pos() -> Result<()> {
1803 let server = MockServer::start().await;
1804
1805 #[derive(Deserialize)]
1806 struct PartialRequest {
1807 txn_id: Option<String>,
1808 }
1809
1810 let server_pos = Arc::new(Mutex::new(0));
1811 let _mock_guard = Mock::given(SlidingSyncMatcher)
1812 .respond_with(move |request: &Request| {
1813 let request: PartialRequest = request.body_json().unwrap();
1815 let pos = {
1816 let mut pos = server_pos.lock().unwrap();
1817 let prev = *pos;
1818 *pos += 1;
1819 prev
1820 };
1821
1822 ResponseTemplate::new(200).set_body_json(json!({
1823 "txn_id": request.txn_id,
1824 "pos": pos.to_string(),
1825 }))
1826 })
1827 .mount_as_scoped(&server)
1828 .await;
1829
1830 let client = logged_in_client(Some(server.uri())).await;
1831
1832 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
1833
1834 {
1836 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1837
1838 assert!(request.pos.is_none());
1839 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1840 }
1841
1842 let sync = sliding_sync.sync();
1843 pin_mut!(sync);
1844
1845 let next = sync.next().await;
1848 assert_matches!(next, Some(Ok(_update_summary)));
1849
1850 assert_eq!(sliding_sync.inner.position.lock().await.pos, Some("0".to_owned()));
1851
1852 let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
1853 .await?
1854 .expect("must have restored fields");
1855
1856 assert_eq!(restored_fields.pos.as_deref(), Some("0"));
1859
1860 {
1862 let other_sync = client.sliding_sync("elephant-sync")?.build().await?;
1863
1864 let mut position_guard = other_sync.inner.position.lock().await;
1865 position_guard.pos = Some("42".to_owned());
1866
1867 other_sync.cache_to_storage(&position_guard).await?;
1868 }
1869
1870 {
1872 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1873 assert_eq!(request.pos.as_deref(), Some("42"));
1874 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
1875 }
1876
1877 {
1879 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
1880 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
1881
1882 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1883 assert_eq!(request.pos.as_deref(), Some("42"));
1884 }
1885
1886 sliding_sync.expire_session().await;
1889
1890 {
1891 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1892
1893 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1894 assert!(request.pos.is_none());
1895 }
1896
1897 {
1899 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
1900 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1901
1902 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1903 assert!(request.pos.is_none());
1904 }
1905
1906 Ok(())
1907 }
1908
1909 #[async_test]
1910 async fn test_stop_sync_loop() -> Result<()> {
1911 let (_server, sliding_sync) = new_sliding_sync(vec![
1912 SlidingSyncList::builder("foo")
1913 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1914 ])
1915 .await?;
1916
1917 let stream = sliding_sync.sync();
1919 pin_mut!(stream);
1920
1921 assert!(stream.next().await.is_some());
1923
1924 sliding_sync.stop_sync()?;
1926
1927 assert!(stream.next().await.is_none());
1929
1930 let stream = sliding_sync.sync();
1932 pin_mut!(stream);
1933
1934 assert!(stream.next().await.is_some());
1936
1937 Ok(())
1938 }
1939
1940 #[async_test]
1941 async fn test_process_read_receipts() -> Result<()> {
1942 let room = owned_room_id!("!pony:example.org");
1943
1944 let server = MockServer::start().await;
1945 let client = logged_in_client(Some(server.uri())).await;
1946 client.event_cache().subscribe().unwrap();
1947
1948 let sliding_sync = client
1949 .sliding_sync("test")?
1950 .with_receipt_extension(
1951 assign!(http::request::Receipts::default(), { enabled: Some(true) }),
1952 )
1953 .add_list(
1954 SlidingSyncList::builder("all")
1955 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
1956 )
1957 .build()
1958 .await?;
1959
1960 {
1962 let server_response = assign!(http::Response::new("0".to_owned()), {
1963 rooms: BTreeMap::from([(
1964 room.clone(),
1965 http::response::Room::default(),
1966 )])
1967 });
1968
1969 let _summary = {
1970 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
1971 sliding_sync
1972 .handle_response(
1973 server_response.clone(),
1974 &mut pos_guard,
1975 RequestedRequiredStates::default(),
1976 )
1977 .await?
1978 };
1979 }
1980
1981 let server_response = assign!(http::Response::new("1".to_owned()), {
1982 extensions: assign!(http::response::Extensions::default(), {
1983 receipts: assign!(http::response::Receipts::default(), {
1984 rooms: BTreeMap::from([
1985 (
1986 room.clone(),
1987 Raw::from_json_string(
1988 json!({
1989 "room_id": room,
1990 "type": "m.receipt",
1991 "content": {
1992 "$event:bar.org": {
1993 "m.read": {
1994 client.user_id().unwrap(): {
1995 "ts": 1436451550,
1996 }
1997 }
1998 }
1999 }
2000 })
2001 .to_string(),
2002 ).unwrap()
2003 )
2004 ])
2005 })
2006 })
2007 });
2008
2009 let summary = {
2010 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2011 sliding_sync
2012 .handle_response(
2013 server_response.clone(),
2014 &mut pos_guard,
2015 RequestedRequiredStates::default(),
2016 )
2017 .await?
2018 };
2019
2020 assert!(summary.rooms.contains(&room));
2021
2022 Ok(())
2023 }
2024
2025 #[async_test]
2026 async fn test_process_marked_unread_room_account_data() -> Result<()> {
2027 let room_id = owned_room_id!("!unicorn:example.org");
2028
2029 let server = MockServer::start().await;
2030 let client = logged_in_client(Some(server.uri())).await;
2031
2032 let sliding_sync = client
2035 .sliding_sync("test")?
2036 .with_account_data_extension(
2037 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2038 )
2039 .add_list(
2040 SlidingSyncList::builder("all")
2041 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2042 )
2043 .build()
2044 .await?;
2045
2046 {
2048 let server_response = assign!(http::Response::new("0".to_owned()), {
2049 rooms: BTreeMap::from([(
2050 room_id.clone(),
2051 http::response::Room::default(),
2052 )])
2053 });
2054
2055 let _summary = {
2056 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2057 sliding_sync
2058 .handle_response(
2059 server_response.clone(),
2060 &mut pos_guard,
2061 RequestedRequiredStates::default(),
2062 )
2063 .await?
2064 };
2065 }
2066
2067 let server_response = make_mark_unread_response("1", room_id.clone(), true, false);
2071
2072 let update_summary = {
2073 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2074 sliding_sync
2075 .handle_response(
2076 server_response.clone(),
2077 &mut pos_guard,
2078 RequestedRequiredStates::default(),
2079 )
2080 .await?
2081 };
2082
2083 assert!(update_summary.rooms.contains(&room_id));
2086
2087 let room = client.get_room(&room_id).unwrap();
2088
2089 assert!(room.is_marked_unread());
2092
2093 let server_response = make_mark_unread_response("2", room_id.clone(), false, true);
2096
2097 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2098 sliding_sync
2099 .handle_response(
2100 server_response.clone(),
2101 &mut pos_guard,
2102 RequestedRequiredStates::default(),
2103 )
2104 .await?;
2105
2106 let room = client.get_room(&room_id).unwrap();
2107
2108 assert!(!room.is_marked_unread());
2109
2110 Ok(())
2111 }
2112
2113 fn make_mark_unread_response(
2114 response_number: &str,
2115 room_id: OwnedRoomId,
2116 unread: bool,
2117 add_rooms_section: bool,
2118 ) -> http::Response {
2119 let rooms = if add_rooms_section {
2120 BTreeMap::from([(room_id.clone(), http::response::Room::default())])
2121 } else {
2122 BTreeMap::new()
2123 };
2124
2125 let extensions = assign!(http::response::Extensions::default(), {
2126 account_data: assign!(http::response::AccountData::default(), {
2127 rooms: BTreeMap::from([
2128 (
2129 room_id,
2130 vec![
2131 Raw::from_json_string(
2132 json!({
2133 "content": {
2134 "unread": unread
2135 },
2136 "type": "m.marked_unread"
2137 })
2138 .to_string(),
2139 ).unwrap()
2140 ]
2141 )
2142 ])
2143 })
2144 });
2145
2146 assign!(http::Response::new(response_number.to_owned()), { rooms: rooms, extensions: extensions })
2147 }
2148
2149 #[async_test]
2150 async fn test_process_rooms_account_data() -> Result<()> {
2151 let room = owned_room_id!("!pony:example.org");
2152
2153 let server = MockServer::start().await;
2154 let client = logged_in_client(Some(server.uri())).await;
2155
2156 let sliding_sync = client
2157 .sliding_sync("test")?
2158 .with_account_data_extension(
2159 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2160 )
2161 .add_list(
2162 SlidingSyncList::builder("all")
2163 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2164 )
2165 .build()
2166 .await?;
2167
2168 {
2170 let server_response = assign!(http::Response::new("0".to_owned()), {
2171 rooms: BTreeMap::from([(
2172 room.clone(),
2173 http::response::Room::default(),
2174 )])
2175 });
2176
2177 let _summary = {
2178 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2179 sliding_sync
2180 .handle_response(
2181 server_response.clone(),
2182 &mut pos_guard,
2183 RequestedRequiredStates::default(),
2184 )
2185 .await?
2186 };
2187 }
2188
2189 let server_response = assign!(http::Response::new("1".to_owned()), {
2190 extensions: assign!(http::response::Extensions::default(), {
2191 account_data: assign!(http::response::AccountData::default(), {
2192 rooms: BTreeMap::from([
2193 (
2194 room.clone(),
2195 vec![
2196 Raw::from_json_string(
2197 json!({
2198 "content": {
2199 "tags": {
2200 "u.work": {
2201 "order": 0.9
2202 }
2203 }
2204 },
2205 "type": "m.tag"
2206 })
2207 .to_string(),
2208 ).unwrap()
2209 ]
2210 )
2211 ])
2212 })
2213 })
2214 });
2215 let summary = {
2216 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2217 sliding_sync
2218 .handle_response(
2219 server_response.clone(),
2220 &mut pos_guard,
2221 RequestedRequiredStates::default(),
2222 )
2223 .await?
2224 };
2225
2226 assert!(summary.rooms.contains(&room));
2227
2228 Ok(())
2229 }
2230
2231 #[async_test]
2232 #[cfg(feature = "e2e-encryption")]
2233 async fn test_process_only_encryption_events() -> Result<()> {
2234 use ruma::OneTimeKeyAlgorithm;
2235
2236 let room = owned_room_id!("!croissant:example.org");
2237
2238 let server = MockServer::start().await;
2239 let client = logged_in_client(Some(server.uri())).await;
2240
2241 let server_response = assign!(http::Response::new("0".to_owned()), {
2242 rooms: BTreeMap::from([(
2243 room.clone(),
2244 assign!(http::response::Room::default(), {
2245 name: Some("Croissants lovers".to_owned()),
2246 timeline: Vec::new(),
2247 }),
2248 )]),
2249
2250 extensions: assign!(http::response::Extensions::default(), {
2251 e2ee: assign!(http::response::E2EE::default(), {
2252 device_one_time_keys_count: BTreeMap::from([(OneTimeKeyAlgorithm::SignedCurve25519, uint!(42))])
2253 }),
2254 to_device: Some(assign!(http::response::ToDevice::default(), {
2255 next_batch: "to-device-token".to_owned(),
2256 })),
2257 })
2258 });
2259
2260 let sliding_sync = client
2264 .sliding_sync("test")?
2265 .with_to_device_extension(
2266 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2267 )
2268 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2269 .build()
2270 .await?;
2271
2272 {
2273 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2274
2275 sliding_sync
2276 .handle_response(
2277 server_response.clone(),
2278 &mut position_guard,
2279 RequestedRequiredStates::default(),
2280 )
2281 .await?;
2282 }
2283
2284 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2286 assert_eq!(uploaded_key_count, 42);
2287
2288 {
2289 let olm_machine = &*client.olm_machine_for_testing().await;
2290 assert_eq!(
2291 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2292 Some("to-device-token")
2293 );
2294 }
2295
2296 assert!(client.get_room(&room).is_none());
2298
2299 let client = logged_in_client(Some(server.uri())).await;
2302
2303 let sliding_sync = client
2304 .sliding_sync("test")?
2305 .add_list(SlidingSyncList::builder("thelist"))
2306 .build()
2307 .await?;
2308
2309 {
2310 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2311
2312 sliding_sync
2313 .handle_response(
2314 server_response.clone(),
2315 &mut position_guard,
2316 RequestedRequiredStates::default(),
2317 )
2318 .await?;
2319 }
2320
2321 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2323 assert_eq!(uploaded_key_count, 0);
2324
2325 {
2326 let olm_machine = &*client.olm_machine_for_testing().await;
2327 assert_eq!(
2328 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2329 None
2330 );
2331 }
2332
2333 assert!(client.get_room(&room).is_some());
2335
2336 let client = logged_in_client(Some(server.uri())).await;
2338
2339 let sliding_sync = client
2340 .sliding_sync("test")?
2341 .add_list(SlidingSyncList::builder("thelist"))
2342 .with_to_device_extension(
2343 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2344 )
2345 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2346 .build()
2347 .await?;
2348
2349 {
2350 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2351
2352 sliding_sync
2353 .handle_response(
2354 server_response.clone(),
2355 &mut position_guard,
2356 RequestedRequiredStates::default(),
2357 )
2358 .await?;
2359 }
2360
2361 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2363 assert_eq!(uploaded_key_count, 42);
2364
2365 {
2366 let olm_machine = &*client.olm_machine_for_testing().await;
2367 assert_eq!(
2368 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2369 Some("to-device-token")
2370 );
2371 }
2372
2373 assert!(client.get_room(&room).is_some());
2375
2376 Ok(())
2377 }
2378
2379 #[async_test]
2380 async fn test_lock_multiple_requests() -> Result<()> {
2381 let server = MockServer::start().await;
2382 let client = logged_in_client(Some(server.uri())).await;
2383
2384 let pos = Arc::new(Mutex::new(0));
2385 let _mock_guard = Mock::given(SlidingSyncMatcher)
2386 .respond_with(move |_: &Request| {
2387 let mut pos = pos.lock().unwrap();
2388 *pos += 1;
2389 ResponseTemplate::new(200).set_body_json(json!({
2390 "pos": pos.to_string(),
2391 "lists": {},
2392 "rooms": {}
2393 }))
2394 })
2395 .mount_as_scoped(&server)
2396 .await;
2397
2398 let sliding_sync = client
2399 .sliding_sync("test")?
2400 .with_to_device_extension(
2401 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2402 )
2403 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2404 .build()
2405 .await?;
2406
2407 let requests = join_all([sliding_sync.sync_once(), sliding_sync.sync_once()]);
2410
2411 for result in requests.await {
2412 result?;
2413 }
2414
2415 Ok(())
2416 }
2417
2418 #[async_test]
2419 async fn test_aborted_request_doesnt_update_future_requests() -> Result<()> {
2420 let server = MockServer::start().await;
2421 let client = logged_in_client(Some(server.uri())).await;
2422
2423 let pos = Arc::new(Mutex::new(0));
2424 let _mock_guard = Mock::given(SlidingSyncMatcher)
2425 .respond_with(move |_: &Request| {
2426 let mut pos = pos.lock().unwrap();
2427 *pos += 1;
2428 ResponseTemplate::new(200)
2430 .set_body_json(json!({
2431 "pos": pos.to_string(),
2432 "lists": {},
2433 "rooms": {}
2434 }))
2435 .set_delay(Duration::from_secs(2))
2436 })
2437 .mount_as_scoped(&server)
2438 .await;
2439
2440 let sliding_sync =
2441 client
2442 .sliding_sync("test")?
2443 .add_list(SlidingSyncList::builder("room-list").sync_mode(
2444 SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(100),
2445 ))
2446 .add_list(
2447 SlidingSyncList::builder("another-list")
2448 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2449 )
2450 .build()
2451 .await?;
2452
2453 let stream = sliding_sync.sync();
2454 pin_mut!(stream);
2455
2456 let cloned_sync = sliding_sync.clone();
2457 spawn(async move {
2458 tokio::time::sleep(Duration::from_millis(100)).await;
2459
2460 cloned_sync
2461 .on_list("another-list", |list| {
2462 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(10..=20));
2463 ready(())
2464 })
2465 .await;
2466 });
2467
2468 assert_matches!(stream.next().await, Some(Ok(_)));
2469
2470 sliding_sync.stop_sync().unwrap();
2471
2472 assert_matches!(stream.next().await, None);
2473
2474 let mut num_requests = 0;
2475
2476 for request in server.received_requests().await.unwrap() {
2477 if !SlidingSyncMatcher.matches(&request) {
2478 continue;
2479 }
2480
2481 let another_list_ranges = if num_requests == 0 {
2482 json!([[0, 10]])
2484 } else {
2485 json!([[10, 20]])
2487 };
2488
2489 num_requests += 1;
2490 assert!(num_requests <= 2, "more than one request hit the server");
2491
2492 let json_value = serde_json::from_slice::<serde_json::Value>(&request.body).unwrap();
2493
2494 if let Err(err) = assert_json_diff::assert_json_matches_no_panic(
2495 &json_value,
2496 &json!({
2497 "conn_id": "test",
2498 "lists": {
2499 "room-list": {
2500 "ranges": [[0, 9]],
2501 "required_state": [
2502 ["m.room.encryption", ""],
2503 ["m.room.tombstone", ""]
2504 ],
2505 },
2506 "another-list": {
2507 "ranges": another_list_ranges,
2508 "required_state": [
2509 ["m.room.encryption", ""],
2510 ["m.room.tombstone", ""]
2511 ],
2512 },
2513 }
2514 }),
2515 assert_json_diff::Config::new(assert_json_diff::CompareMode::Inclusive),
2516 ) {
2517 dbg!(json_value);
2518 panic!("json differ: {err}");
2519 }
2520 }
2521
2522 assert_eq!(num_requests, 2);
2523
2524 Ok(())
2525 }
2526
2527 #[async_test]
2528 async fn test_timeout_zero_list() -> Result<()> {
2529 let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
2530
2531 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2532
2533 assert!(request.timeout.is_some());
2536
2537 Ok(())
2538 }
2539
2540 #[async_test]
2541 async fn test_timeout_one_list() -> Result<()> {
2542 let (_server, sliding_sync) = new_sliding_sync(vec![
2543 SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2544 ])
2545 .await?;
2546
2547 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2548
2549 assert!(request.timeout.is_none());
2551
2552 {
2554 let server_response = assign!(http::Response::new("0".to_owned()), {
2555 lists: BTreeMap::from([(
2556 "foo".to_owned(),
2557 assign!(http::response::List::default(), {
2558 count: uint!(7),
2559 })
2560 )])
2561 });
2562
2563 let _summary = {
2564 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2565 sliding_sync
2566 .handle_response(
2567 server_response.clone(),
2568 &mut pos_guard,
2569 RequestedRequiredStates::default(),
2570 )
2571 .await?
2572 };
2573 }
2574
2575 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2576
2577 assert!(request.timeout.is_some());
2579
2580 Ok(())
2581 }
2582
2583 #[async_test]
2584 async fn test_timeout_three_lists() -> Result<()> {
2585 let (_server, sliding_sync) = new_sliding_sync(vec![
2586 SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2587 SlidingSyncList::builder("bar").sync_mode(SlidingSyncMode::new_paging(10)),
2588 SlidingSyncList::builder("baz")
2589 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2590 ])
2591 .await?;
2592
2593 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2594
2595 assert!(request.timeout.is_none());
2597
2598 {
2600 let server_response = assign!(http::Response::new("0".to_owned()), {
2601 lists: BTreeMap::from([(
2602 "foo".to_owned(),
2603 assign!(http::response::List::default(), {
2604 count: uint!(7),
2605 })
2606 )])
2607 });
2608
2609 let _summary = {
2610 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2611 sliding_sync
2612 .handle_response(
2613 server_response.clone(),
2614 &mut pos_guard,
2615 RequestedRequiredStates::default(),
2616 )
2617 .await?
2618 };
2619 }
2620
2621 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2622
2623 assert!(request.timeout.is_none());
2625
2626 {
2628 let server_response = assign!(http::Response::new("1".to_owned()), {
2629 lists: BTreeMap::from([(
2630 "bar".to_owned(),
2631 assign!(http::response::List::default(), {
2632 count: uint!(7),
2633 })
2634 )])
2635 });
2636
2637 let _summary = {
2638 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2639 sliding_sync
2640 .handle_response(
2641 server_response.clone(),
2642 &mut pos_guard,
2643 RequestedRequiredStates::default(),
2644 )
2645 .await?
2646 };
2647 }
2648
2649 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2650
2651 assert!(request.timeout.is_some());
2653
2654 Ok(())
2655 }
2656
2657 #[async_test]
2658 async fn test_sync_beat_is_notified_on_sync_response() -> Result<()> {
2659 let server = MockServer::start().await;
2660 let client = logged_in_client(Some(server.uri())).await;
2661
2662 let _mock_guard = Mock::given(SlidingSyncMatcher)
2663 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2664 "pos": "0",
2665 "lists": {},
2666 "rooms": {}
2667 })))
2668 .mount_as_scoped(&server)
2669 .await;
2670
2671 let sliding_sync = client
2672 .sliding_sync("test")?
2673 .with_to_device_extension(
2674 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2675 )
2676 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2677 .build()
2678 .await?;
2679
2680 let sliding_sync = Arc::new(sliding_sync);
2681
2682 let sync_beat_listener = client.inner.sync_beat.listen();
2684 sliding_sync.sync_once().await?;
2685
2686 assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_some());
2688 Ok(())
2689 }
2690
2691 #[async_test]
2692 async fn test_sync_beat_is_not_notified_on_sync_failure() -> Result<()> {
2693 let server = MockServer::start().await;
2694 let client = logged_in_client(Some(server.uri())).await;
2695
2696 let _mock_guard = Mock::given(SlidingSyncMatcher)
2697 .respond_with(ResponseTemplate::new(404))
2698 .mount_as_scoped(&server)
2699 .await;
2700
2701 let sliding_sync = client
2702 .sliding_sync("test")?
2703 .with_to_device_extension(
2704 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2705 )
2706 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2707 .build()
2708 .await?;
2709
2710 let sliding_sync = Arc::new(sliding_sync);
2711
2712 let sync_beat_listener = client.inner.sync_beat.listen();
2714 let sync_result = sliding_sync.sync_once().await;
2715 assert!(sync_result.is_err());
2716
2717 assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_none());
2719
2720 Ok(())
2721 }
2722
2723 #[async_test]
2724 async fn test_state_store_lock_is_released_before_calling_handlers() -> Result<()> {
2725 let server = MatrixMockServer::new().await;
2726 let client = server.client_builder().build().await;
2727 let room_id = room_id!("!mu5hr00m:example.org");
2728
2729 let _sync_mock_guard = Mock::given(SlidingSyncMatcher)
2730 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2731 "pos": "0",
2732 "lists": {},
2733 "extensions": {
2734 "account_data": {
2735 "global": [
2736 {
2737 "type": "m.direct",
2738 "content": {
2739 "@de4dlockh0lmes:example.org": [
2740 "!mu5hr00m:example.org"
2741 ]
2742 }
2743 }
2744 ]
2745 }
2746 },
2747 "rooms": {
2748 room_id: {
2749 "name": "Mario Bros Fanbase Room",
2750 "initial": true,
2751 },
2752 }
2753 })))
2754 .mount_as_scoped(server.server())
2755 .await;
2756
2757 let f = EventFactory::new().room(room_id);
2758
2759 Mock::given(method("GET"))
2760 .and(wiremock::matchers::path_regex(r"/_matrix/client/v3/rooms/.*/members"))
2761 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2762 "chunk": [
2763 f.member(&ALICE).membership(MembershipState::Join).into_raw_timeline(),
2764 ]
2765 })))
2766 .mount(server.server())
2767 .await;
2768
2769 let (tx, rx) = tokio::sync::oneshot::channel();
2770
2771 let tx = Arc::new(Mutex::new(Some(tx)));
2772 client.add_event_handler(move |_: DirectEvent, client: Client| async move {
2773 let members =
2775 client.get_room(room_id).unwrap().members(RoomMemberships::JOIN).await.unwrap();
2776 assert_eq!(members.len(), 1);
2777 tx.lock().unwrap().take().expect("sender consumed multiple times").send(()).unwrap();
2778 });
2779
2780 let sliding_sync = client
2781 .sliding_sync("test")?
2782 .add_list(SlidingSyncList::builder("thelist"))
2783 .with_account_data_extension(
2784 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2785 )
2786 .build()
2787 .await?;
2788
2789 tokio::time::timeout(Duration::from_secs(5), sliding_sync.sync_once())
2790 .await
2791 .expect("Sync did not complete in time")
2792 .expect("Sync failed");
2793
2794 tokio::time::timeout(Duration::from_secs(5), rx)
2796 .await
2797 .expect("Event handler did not complete in time")
2798 .expect("Event handler failed");
2799
2800 Ok(())
2801 }
2802}