1use std::{
31 cmp::Ordering,
32 collections::{HashMap, HashSet, VecDeque},
33 sync::Arc,
34};
35
36use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
37use futures_util::{future::join_all, pin_mut};
38use imbl::Vector;
39use itertools::Itertools;
40use matrix_sdk::{
41 Client, Error as SDKError, Room, deserialized_responses::SyncOrStrippedState,
42 task_monitor::BackgroundTaskHandle,
43};
44use ruma::{
45 OwnedRoomId, RoomId, SpaceChildOrder,
46 events::{
47 self, StateEventType, SyncStateEvent,
48 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
49 },
50};
51use thiserror::Error;
52use tokio::sync::Mutex as AsyncMutex;
53use tracing::{error, trace, warn};
54
55use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle, room::SpaceRoomChildState};
56pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
57
58pub mod graph;
59pub mod leave;
60pub mod room;
61pub mod room_list;
62
63#[derive(Debug, Error)]
65pub enum Error {
66 #[error("User ID not available from client")]
68 UserIdNotFound,
69
70 #[error("Room `{0}` not found")]
72 RoomNotFound(OwnedRoomId),
73
74 #[error("Missing `{0}` for `{1}`")]
76 MissingState(StateEventType, OwnedRoomId),
77
78 #[error("Failed to set either of the m.space.parent or m.space.child state events")]
81 UpdateRelationship(SDKError),
82
83 #[error(
86 "Failed to set the expected m.space.parent state event (but any m.space.child changes were successful)"
87 )]
88 UpdateInverseRelationship(SDKError),
89
90 #[error("Failed to leave space")]
92 LeaveSpace(SDKError),
93
94 #[error("Failed to load members")]
96 LoadRoomMembers(SDKError),
97}
98
99struct SpaceState {
100 graph: SpaceGraph,
101 top_level_joined_spaces: ObservableVector<SpaceRoom>,
102 space_filters: ObservableVector<SpaceFilter>,
103}
104
105pub struct SpaceService {
145 client: Client,
146
147 space_state: Arc<AsyncMutex<SpaceState>>,
148
149 _room_update_handle: AsyncMutex<BackgroundTaskHandle>,
150}
151
152impl SpaceService {
153 pub async fn new(client: Client) -> Self {
155 let space_state = Arc::new(AsyncMutex::new(SpaceState {
156 graph: SpaceGraph::new(),
157 top_level_joined_spaces: ObservableVector::new(),
158 space_filters: ObservableVector::new(),
159 }));
160
161 let room_update_handle = client
162 .task_monitor()
163 .spawn_infinite_task("space_service", {
164 let client = client.clone();
165 let space_state = Arc::clone(&space_state);
166 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
167
168 async move {
169 pin_mut!(all_room_updates_receiver);
170
171 loop {
172 match all_room_updates_receiver.recv().await {
173 Ok(updates) => {
174 if updates.is_empty() {
175 continue;
176 }
177
178 let (spaces, filters, graph) =
179 Self::build_space_state(&client).await;
180 Self::update_space_state_if_needed(
181 Vector::from(spaces),
182 Vector::from(filters),
183 graph,
184 &space_state,
185 )
186 .await;
187 }
188 Err(err) => {
189 error!("error when listening to room updates: {err}");
190 }
191 }
192 }
193 }
194 })
195 .abort_on_drop();
196
197 let (spaces, filters, graph) = Self::build_space_state(&client).await;
199 Self::update_space_state_if_needed(
200 Vector::from(spaces),
201 Vector::from(filters),
202 graph,
203 &space_state,
204 )
205 .await;
206
207 Self { client, space_state, _room_update_handle: AsyncMutex::new(room_update_handle) }
208 }
209
210 pub async fn subscribe_to_top_level_joined_spaces(
213 &self,
214 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
215 self.space_state
216 .lock()
217 .await
218 .top_level_joined_spaces
219 .subscribe()
220 .into_values_and_batched_stream()
221 }
222
223 pub async fn top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
227 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
228
229 Self::update_space_state_if_needed(
230 Vector::from(top_level_joined_spaces.clone()),
231 Vector::from(filters),
232 graph,
233 &self.space_state,
234 )
235 .await;
236
237 top_level_joined_spaces
238 }
239
240 pub async fn space_filters(&self) -> Vec<SpaceFilter> {
285 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
286
287 Self::update_space_state_if_needed(
288 Vector::from(top_level_joined_spaces),
289 Vector::from(filters.clone()),
290 graph,
291 &self.space_state,
292 )
293 .await;
294
295 filters
296 }
297
298 pub async fn subscribe_to_space_filters(
300 &self,
301 ) -> (Vector<SpaceFilter>, VectorSubscriberBatchedStream<SpaceFilter>) {
302 self.space_state.lock().await.space_filters.subscribe().into_values_and_batched_stream()
303 }
304
305 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
311 let Some(user_id) = self.client.user_id() else {
312 return vec![];
313 };
314
315 let graph = &self.space_state.lock().await.graph;
316 let rooms = self.client.joined_space_rooms();
317
318 let mut editable_spaces = Vec::new();
319 for room in &rooms {
320 if let Ok(power_levels) = room.power_levels().await
321 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
322 {
323 let room_id = room.room_id();
324 editable_spaces.push(
325 SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64).await,
326 );
327 }
328 }
329
330 editable_spaces
331 }
332
333 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
335 SpaceRoomList::new(self.client.clone(), space_id).await
336 }
337
338 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
340 let graph = &self.space_state.lock().await.graph;
341
342 let rooms = graph
343 .parents_of(child_id)
344 .into_iter()
345 .filter_map(|parent_id| self.client.get_room(parent_id));
346
347 join_all(rooms.map(|room| async move {
348 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64).await
349 }))
350 .await
351 }
352
353 pub async fn joined_parent_ids_of_child(&self, child_id: &RoomId) -> Vec<OwnedRoomId> {
370 self.space_state
371 .lock()
372 .await
373 .graph
374 .parents_of(child_id)
375 .into_iter()
376 .map(ToOwned::to_owned)
377 .collect()
378 }
379
380 pub async fn top_level_ancestors_of(&self, child_id: &RoomId) -> HashSet<OwnedRoomId> {
396 let space_state = self.space_state.lock().await;
397 let graph = &space_state.graph;
398
399 if !graph.has_node(child_id) {
400 return HashSet::new();
401 }
402
403 let mut queue = VecDeque::from([child_id.to_owned()]);
404 let mut visited = HashSet::from([child_id.to_owned()]);
405 let mut roots = HashSet::new();
406 while let Some(current) = queue.pop_front() {
407 let parents = graph.parents_of(¤t);
408 if parents.is_empty() {
409 roots.insert(current);
411 continue;
412 }
413 for parent in parents {
414 if visited.insert(parent.to_owned()) {
415 queue.push_back(parent.to_owned());
416 }
417 }
418 }
419 roots
420 }
421
422 pub async fn get_space_room(&self, room_id: &RoomId) -> Option<SpaceRoom> {
425 let graph = &self.space_state.lock().await.graph;
426
427 if graph.has_node(room_id)
428 && let Some(room) = self.client.get_room(room_id)
429 {
430 Some(
431 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64)
432 .await,
433 )
434 } else {
435 None
436 }
437 }
438
439 pub async fn add_child_to_space(
440 &self,
441 child_id: OwnedRoomId,
442 space_id: OwnedRoomId,
443 ) -> Result<(), Error> {
444 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
445 let space_room =
446 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
447 let child_room =
448 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
449 let child_power_levels = child_room
450 .power_levels()
451 .await
452 .map_err(|error| Error::UpdateRelationship(matrix_sdk::Error::from(error)))?;
453
454 let child_route = child_room.route().await.map_err(Error::UpdateRelationship)?;
456 space_room
457 .send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
458 .await
459 .map_err(Error::UpdateRelationship)?;
460
461 if child_power_levels.user_can_send_state(user_id, StateEventType::SpaceParent) {
463 let parent_route =
464 space_room.route().await.map_err(Error::UpdateInverseRelationship)?;
465 child_room
466 .send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
467 .await
468 .map_err(Error::UpdateInverseRelationship)?;
469 } else {
470 warn!("The current user doesn't have permission to set the child's parent.");
471 }
472
473 Ok(())
474 }
475
476 pub async fn remove_child_from_space(
477 &self,
478 child_id: OwnedRoomId,
479 space_id: OwnedRoomId,
480 ) -> Result<(), Error> {
481 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
482 let space_room =
483 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
484
485 if let Ok(Some(_)) =
486 space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
487 {
488 space_room
495 .send_state_event_raw("m.space.child", child_id.as_str(), serde_json::json!({}))
496 .await
497 .map_err(Error::UpdateRelationship)?;
498 } else {
499 warn!("A space child event wasn't found on the parent, ignoring.");
500 }
501
502 if let Some(child_room) = self.client.get_room(&child_id) {
503 let power_levels = child_room.power_levels().await.map_err(|error| {
504 Error::UpdateInverseRelationship(matrix_sdk::Error::from(error))
505 })?;
506
507 if power_levels.user_can_send_state(user_id, StateEventType::SpaceParent)
508 && let Ok(Some(_)) = child_room
509 .get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id)
510 .await
511 {
512 child_room
514 .send_state_event_raw(
515 "m.space.parent",
516 space_id.as_str(),
517 serde_json::json!({}),
518 )
519 .await
520 .map_err(Error::UpdateInverseRelationship)?;
521 } else {
522 warn!("A space parent event wasn't found on the child, ignoring.");
523 }
524 } else {
525 warn!("The child room is unknown, skipping m.space.parent removal.");
526 }
527
528 Ok(())
529 }
530
531 pub async fn leave_space(&self, space_id: &RoomId) -> Result<LeaveSpaceHandle, Error> {
539 let space_state = self.space_state.lock().await;
540
541 if !space_state.graph.has_node(space_id) {
542 return Err(Error::RoomNotFound(space_id.to_owned()));
543 }
544
545 let room_ids = space_state.graph.flattened_bottom_up_subtree(space_id);
546
547 let handle = LeaveSpaceHandle::new(self.client.clone(), room_ids).await;
548
549 Ok(handle)
550 }
551
552 async fn update_space_state_if_needed(
553 new_spaces: Vector<SpaceRoom>,
554 new_filters: Vector<SpaceFilter>,
555 new_graph: SpaceGraph,
556 space_state: &Arc<AsyncMutex<SpaceState>>,
557 ) {
558 let mut space_state = space_state.lock().await;
559
560 if new_spaces != space_state.top_level_joined_spaces.clone() {
561 space_state.top_level_joined_spaces.clear();
562 space_state.top_level_joined_spaces.append(new_spaces);
563 }
564
565 if new_filters != space_state.space_filters.clone() {
566 space_state.space_filters.clear();
567 space_state.space_filters.append(new_filters);
568 }
569
570 space_state.graph = new_graph;
571 }
572
573 async fn build_space_state(client: &Client) -> (Vec<SpaceRoom>, Vec<SpaceFilter>, SpaceGraph) {
574 let joined_spaces = client.joined_space_rooms();
575 let joined_space_ids =
576 joined_spaces.iter().map(|space| space.room_id()).collect::<HashSet<_>>();
577
578 let mut graph = SpaceGraph::new();
580
581 let mut space_child_states = HashMap::<OwnedRoomId, SpaceRoomChildState>::new();
583
584 for space in joined_spaces.iter() {
587 graph.add_node(space.room_id().to_owned());
588
589 if let Ok(parents) = space.get_state_events_static::<SpaceParentEventContent>().await {
590 parents.into_iter()
591 .flat_map(|parent_event| match parent_event.deserialize() {
592 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
593 Some(e.state_key)
594 }
595 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
596 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
597 Err(e) => {
598 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.parent: {e}");
599 None
600 }
601 })
602 .filter(|parent| joined_space_ids.contains(&**parent))
608 .for_each(|parent| graph.add_edge(parent, space.room_id().to_owned()));
609 } else {
610 error!(room_id = ?space.room_id(), "Could not get m.space.parent events");
611 }
612
613 if let Ok(children) = space.get_state_events_static::<SpaceChildEventContent>().await {
614 children.into_iter()
615 .filter_map(|child_event| match child_event.deserialize() {
616 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
617 space_child_states.insert(
618 e.state_key.to_owned(),
619 SpaceRoomChildState {
620 order: e.content.order.clone(),
621 origin_server_ts: e.origin_server_ts,
622 },
623 );
624
625 Some(e.state_key)
626 }
627 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
628 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
629 Err(e) => {
630 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.child: {e}");
631 None
632 }
633 }).for_each(|child| graph.add_edge(space.room_id().to_owned(), child));
634 } else {
635 error!(room_id = ?space.room_id(), "Could not get m.space.child events");
636 }
637 }
638
639 graph.remove_cycles();
642
643 let root_nodes = graph.root_nodes();
644
645 let top_level_space_rooms = joined_spaces
649 .iter()
650 .filter(|room| root_nodes.contains(&room.room_id()))
651 .collect::<Vec<_>>();
652
653 let mut top_level_space_order = HashMap::new();
654 for space in &top_level_space_rooms {
655 if let Ok(Some(raw_event)) =
656 space.account_data_static::<events::space_order::SpaceOrderEventContent>().await
657 && let Ok(event) = raw_event.deserialize()
658 {
659 top_level_space_order.insert(space.room_id().to_owned(), event.content.order);
660 }
661 }
662
663 let top_level_space_rooms = top_level_space_rooms
664 .into_iter()
665 .sorted_by(|a, b| {
666 let a = (a.room_id(), top_level_space_order.get(a.room_id()).map(AsRef::as_ref));
667 let b = (b.room_id(), top_level_space_order.get(b.room_id()).map(AsRef::as_ref));
668
669 compare_top_level_space_rooms(a, b)
670 })
671 .collect::<Vec<_>>();
672
673 let mut top_level_spaces = Vec::new();
674
675 for room in &top_level_space_rooms {
676 top_level_spaces.push(
677 SpaceRoom::new_from_known(room, graph.children_of(room.room_id()).len() as u64)
678 .await,
679 );
680 }
681
682 let space_filters =
683 Self::build_space_filters(client, &graph, top_level_space_rooms, space_child_states)
684 .await;
685
686 (top_level_spaces, space_filters, graph)
687 }
688
689 async fn build_space_filters(
698 client: &Client,
699 graph: &SpaceGraph,
700 top_level_space_rooms: Vec<&Room>,
701 space_child_states: HashMap<OwnedRoomId, SpaceRoomChildState>,
702 ) -> Vec<SpaceFilter> {
703 let mut filters = Vec::new();
704 for top_level_space in top_level_space_rooms {
705 let children = graph
706 .children_of(top_level_space.room_id())
707 .into_iter()
708 .map(|id| id.to_owned())
709 .collect::<Vec<_>>();
710
711 filters.push(SpaceFilter {
712 space_room: SpaceRoom::new_from_known(top_level_space, children.len() as u64).await,
713 level: 0,
714 descendants: children.clone(),
715 });
716
717 let children_rooms = join_all(
718 children
719 .iter()
720 .filter_map(|child| client.get_room(child))
721 .filter(|room| room.is_space())
722 .map(|room| async move {
723 SpaceRoom::new_from_known(
724 &room,
725 graph.children_of(room.room_id()).len() as u64,
726 )
727 .await
728 }),
729 )
730 .await;
731 filters.append(
732 &mut children_rooms
733 .into_iter()
734 .sorted_by(|a, b| {
735 let a_state = space_child_states.get(&a.room_id).cloned();
736 let b_state = space_child_states.get(&b.room_id).cloned();
737
738 SpaceRoom::compare_rooms(
739 (&a.room_id, a_state.as_ref()),
740 (&b.room_id, b_state.as_ref()),
741 )
742 })
743 .map(|space_room| {
744 let descendants = graph.flattened_bottom_up_subtree(&space_room.room_id);
745
746 SpaceFilter { space_room, level: 1, descendants }
747 })
748 .collect::<Vec<_>>(),
749 );
750 }
751
752 filters
753 }
754}
755
756fn compare_top_level_space_rooms(
758 a: (&RoomId, Option<&SpaceChildOrder>),
759 b: (&RoomId, Option<&SpaceChildOrder>),
760) -> Ordering {
761 let (a_room_id, a_order) = a;
762 let (b_room_id, b_order) = b;
763
764 match (a_order, b_order) {
765 (Some(a_order), Some(b_order)) => a_order.cmp(b_order).then(a_room_id.cmp(b_room_id)),
766 (Some(_), None) => Ordering::Less,
767 (None, Some(_)) => Ordering::Greater,
768 (None, None) => a_room_id.cmp(b_room_id),
769 }
770}
771
772#[derive(Debug, Clone, PartialEq)]
773pub struct SpaceFilter {
774 pub space_room: SpaceRoom,
776
777 pub level: u8,
780
781 pub descendants: Vec<OwnedRoomId>,
785}
786
787#[cfg(test)]
788mod tests {
789 use std::collections::BTreeMap;
790
791 use assert_matches2::assert_let;
792 use eyeball_im::VectorDiff;
793 use futures_util::{StreamExt, pin_mut};
794 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
795 use matrix_sdk_test::{
796 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
797 };
798 use proptest::prelude::*;
799 use ruma::{
800 MilliSecondsSinceUnixEpoch, OwnedSpaceChildOrder, RoomVersionId, UserId, event_id,
801 owned_room_id, room_id, serde::Raw,
802 };
803 use serde_json::json;
804 use stream_assert::{assert_next_eq, assert_pending};
805
806 use super::*;
807
808 #[async_test]
809 async fn test_spaces_hierarchy() {
810 let server = MatrixMockServer::new().await;
811 let client = server.client_builder().build().await;
812 let user_id = client.user_id().unwrap();
813 let space_service = SpaceService::new(client.clone()).await;
814 let factory = EventFactory::new();
815
816 server.mock_room_state_encryption().plain().mount().await;
817
818 let parent_space_id = room_id!("!parent_space:example.org");
821 let child_space_id_1 = room_id!("!child_space_1:example.org");
822 let child_space_id_2 = room_id!("!child_space_2:example.org");
823
824 add_space_rooms(
825 vec![
826 MockSpaceRoomParameters {
827 room_id: child_space_id_1,
828 order: None,
829 parents: vec![parent_space_id],
830 children: vec![],
831 power_level: None,
832 },
833 MockSpaceRoomParameters {
834 room_id: child_space_id_2,
835 order: None,
836 parents: vec![parent_space_id],
837 children: vec![],
838 power_level: None,
839 },
840 MockSpaceRoomParameters {
841 room_id: parent_space_id,
842 order: None,
843 parents: vec![],
844 children: vec![child_space_id_1, child_space_id_2],
845 power_level: None,
846 },
847 ],
848 &client,
849 &server,
850 &factory,
851 user_id,
852 )
853 .await;
854
855 assert_eq!(
857 space_service
858 .top_level_joined_spaces()
859 .await
860 .iter()
861 .map(|s| s.room_id.to_owned())
862 .collect::<Vec<_>>(),
863 vec![parent_space_id]
864 );
865
866 assert_eq!(
868 space_service
869 .top_level_joined_spaces()
870 .await
871 .iter()
872 .map(|s| s.children_count)
873 .collect::<Vec<_>>(),
874 vec![2]
875 );
876
877 let parent_space = client.get_room(parent_space_id).unwrap();
878 assert!(parent_space.is_space());
879
880 let spaces: Vec<ParentSpace> = client
883 .get_room(child_space_id_1)
884 .unwrap()
885 .parent_spaces()
886 .await
887 .unwrap()
888 .map(Result::unwrap)
889 .collect()
890 .await;
891
892 assert_let!(ParentSpace::Reciprocal(parent) = spaces.first().unwrap());
893 assert_eq!(parent.room_id(), parent_space.room_id());
894
895 let spaces: Vec<ParentSpace> = client
896 .get_room(child_space_id_2)
897 .unwrap()
898 .parent_spaces()
899 .await
900 .unwrap()
901 .map(Result::unwrap)
902 .collect()
903 .await;
904
905 assert_let!(ParentSpace::Reciprocal(parent) = spaces.last().unwrap());
906 assert_eq!(parent.room_id(), parent_space.room_id());
907 }
908
909 #[async_test]
910 async fn test_joined_spaces_updates() {
911 let server = MatrixMockServer::new().await;
912 let client = server.client_builder().build().await;
913 let user_id = client.user_id().unwrap();
914 let factory = EventFactory::new();
915
916 server.mock_room_state_encryption().plain().mount().await;
917
918 let first_space_id = room_id!("!first_space:example.org");
919 let second_space_id = room_id!("!second_space:example.org");
920
921 server
923 .sync_room(
924 &client,
925 JoinedRoomBuilder::new(first_space_id)
926 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type()),
927 )
928 .await;
929
930 let space_service = SpaceService::new(client.clone()).await;
934
935 let (initial_values, joined_spaces_subscriber) =
936 space_service.subscribe_to_top_level_joined_spaces().await;
937 pin_mut!(joined_spaces_subscriber);
938 assert_pending!(joined_spaces_subscriber);
939
940 assert_eq!(
941 initial_values,
942 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
943 .into()
944 );
945
946 assert_eq!(
947 space_service.top_level_joined_spaces().await,
948 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
949 );
950
951 assert_pending!(joined_spaces_subscriber);
954
955 server
958 .sync_room(
959 &client,
960 JoinedRoomBuilder::new(second_space_id)
961 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
962 .add_state_event(
963 factory
964 .space_child(
965 second_space_id.to_owned(),
966 owned_room_id!("!child:example.org"),
967 )
968 .sender(user_id),
969 ),
970 )
971 .await;
972
973 assert_eq!(
975 space_service.top_level_joined_spaces().await,
976 vec![
977 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await,
978 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1).await
979 ]
980 );
981
982 assert_next_eq!(
983 joined_spaces_subscriber,
984 vec![
985 VectorDiff::Clear,
986 VectorDiff::Append {
987 values: vec![
988 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)
989 .await,
990 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
991 .await
992 ]
993 .into()
994 },
995 ]
996 );
997
998 server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
999
1000 assert_next_eq!(
1002 joined_spaces_subscriber,
1003 vec![
1004 VectorDiff::Clear,
1005 VectorDiff::Append {
1006 values: vec![
1007 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)
1008 .await
1009 ]
1010 .into()
1011 },
1012 ]
1013 );
1014
1015 server
1017 .sync_room(
1018 &client,
1019 JoinedRoomBuilder::new(room_id!("!room:example.org"))
1020 .add_state_event(factory.create(user_id, RoomVersionId::V1)),
1021 )
1022 .await;
1023
1024 assert_pending!(joined_spaces_subscriber);
1026 assert_eq!(
1027 space_service.top_level_joined_spaces().await,
1028 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
1029 );
1030 }
1031
1032 #[async_test]
1033 async fn test_joined_child_space_becomes_top_level_after_leaving_parent() {
1034 let server = MatrixMockServer::new().await;
1035 let client = server.client_builder().build().await;
1036
1037 server.mock_room_state_encryption().plain().mount().await;
1038
1039 let parent_space_id = room_id!("!parent_space:example.org");
1040 let child_space_id = room_id!("!child_space:example.org");
1041 let child_room_id = room_id!("!child_room:example.org");
1042
1043 add_space_rooms(
1044 vec![
1045 MockSpaceRoomParameters {
1046 room_id: child_space_id,
1047 order: None,
1048 parents: vec![parent_space_id],
1049 children: vec![child_room_id],
1050 power_level: None,
1051 },
1052 MockSpaceRoomParameters {
1053 room_id: parent_space_id,
1054 order: None,
1055 parents: vec![],
1056 children: vec![child_space_id],
1057 power_level: None,
1058 },
1059 ],
1060 &client,
1061 &server,
1062 &EventFactory::new(),
1063 client.user_id().unwrap(),
1064 )
1065 .await;
1066
1067 let space_service = SpaceService::new(client.clone()).await;
1068
1069 let top_level_spaces = space_service.top_level_joined_spaces().await;
1070 assert_eq!(top_level_spaces.len(), 1);
1071 assert_eq!(top_level_spaces[0].room_id, parent_space_id);
1072
1073 server.sync_room(&client, LeftRoomBuilder::new(parent_space_id)).await;
1074
1075 let top_level_spaces = space_service.top_level_joined_spaces().await;
1076 assert_eq!(top_level_spaces.len(), 1);
1077 assert_eq!(top_level_spaces[0].room_id, child_space_id);
1078 assert_eq!(top_level_spaces[0].children_count, 1);
1079
1080 let filters = space_service.space_filters().await;
1081 assert_eq!(filters.len(), 1);
1082 assert_eq!(filters[0].space_room.room_id, child_space_id);
1083 assert_eq!(filters[0].descendants, vec![child_room_id]);
1084 }
1085
1086 #[async_test]
1087 async fn test_space_filters() {
1088 let server = MatrixMockServer::new().await;
1089 let client = server.client_builder().build().await;
1090
1091 server.mock_room_state_encryption().plain().mount().await;
1092
1093 add_space_rooms(
1094 vec![
1095 MockSpaceRoomParameters {
1096 room_id: room_id!("!1:a.b"),
1097 order: None,
1098 parents: vec![],
1099 children: vec![],
1100 power_level: None,
1101 },
1102 MockSpaceRoomParameters {
1103 room_id: room_id!("!1.2:a.b"),
1104 order: None,
1105 parents: vec![room_id!("!1:a.b")],
1106 children: vec![],
1107 power_level: None,
1108 },
1109 MockSpaceRoomParameters {
1110 room_id: room_id!("!1.2.3:a.b"),
1111 order: None,
1112 parents: vec![room_id!("!1.2:a.b")],
1113 children: vec![],
1114 power_level: None,
1115 },
1116 MockSpaceRoomParameters {
1117 room_id: room_id!("!1.2.3.4:a.b"),
1118 order: None,
1119 parents: vec![room_id!("!1.2.3:a.b")],
1120 children: vec![],
1121 power_level: None,
1122 },
1123 ],
1124 &client,
1125 &server,
1126 &EventFactory::new(),
1127 client.user_id().unwrap(),
1128 )
1129 .await;
1130
1131 let space_service = SpaceService::new(client.clone()).await;
1132
1133 let filters = space_service.space_filters().await;
1134 assert_eq!(filters.len(), 2);
1135 assert_eq!(filters[0].space_room.room_id, "!1:a.b");
1136 assert_eq!(filters[0].level, 0);
1137 assert_eq!(filters[0].descendants.len(), 1); assert_eq!(filters[1].space_room.room_id, "!1.2:a.b");
1139 assert_eq!(filters[1].level, 1);
1140 assert_eq!(filters[1].descendants.len(), 3);
1141
1142 let (initial_values, space_filters_subscriber) =
1143 space_service.subscribe_to_space_filters().await;
1144 pin_mut!(space_filters_subscriber);
1145 assert_pending!(space_filters_subscriber);
1146
1147 assert_eq!(initial_values, filters.into());
1148
1149 add_space_rooms(
1150 vec![MockSpaceRoomParameters {
1151 room_id: room_id!("!1.2.3.4.5:a.b"),
1152 order: None,
1153 parents: vec![room_id!("!1.2.3.4:a.b")],
1154 children: vec![],
1155 power_level: None,
1156 }],
1157 &client,
1158 &server,
1159 &EventFactory::new(),
1160 client.user_id().unwrap(),
1161 )
1162 .await;
1163
1164 space_filters_subscriber.next().await;
1165
1166 let filters = space_service.space_filters().await;
1167 assert_eq!(filters[0].descendants.len(), 1);
1168 assert_eq!(filters[1].descendants.len(), 4);
1169 }
1170
1171 #[async_test]
1172 async fn test_top_level_space_order() {
1173 let server = MatrixMockServer::new().await;
1174 let client = server.client_builder().build().await;
1175
1176 server.mock_room_state_encryption().plain().mount().await;
1177
1178 add_space_rooms(
1179 vec![
1180 MockSpaceRoomParameters {
1181 room_id: room_id!("!2:a.b"),
1182 order: Some("2"),
1183 parents: vec![],
1184 children: vec![],
1185 power_level: None,
1186 },
1187 MockSpaceRoomParameters {
1188 room_id: room_id!("!4:a.b"),
1189 order: None,
1190 parents: vec![],
1191 children: vec![],
1192 power_level: None,
1193 },
1194 MockSpaceRoomParameters {
1195 room_id: room_id!("!3:a.b"),
1196 order: None,
1197 parents: vec![],
1198 children: vec![],
1199 power_level: None,
1200 },
1201 MockSpaceRoomParameters {
1202 room_id: room_id!("!1:a.b"),
1203 order: Some("1"),
1204 parents: vec![],
1205 children: vec![],
1206 power_level: None,
1207 },
1208 ],
1209 &client,
1210 &server,
1211 &EventFactory::new(),
1212 client.user_id().unwrap(),
1213 )
1214 .await;
1215
1216 let space_service = SpaceService::new(client.clone()).await;
1217
1218 assert_eq!(
1221 space_service.top_level_joined_spaces().await,
1222 vec![
1223 SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0).await,
1224 SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0).await,
1225 SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0).await,
1226 SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0).await,
1227 ]
1228 );
1229 }
1230
1231 #[async_test]
1232 async fn test_editable_spaces() {
1233 let server = MatrixMockServer::new().await;
1235 let client = server.client_builder().build().await;
1236 let user_id = client.user_id().unwrap();
1237 let factory = EventFactory::new();
1238
1239 server.mock_room_state_encryption().plain().mount().await;
1240
1241 let admin_space_id = room_id!("!admin_space:example.org");
1242 let admin_subspace_id = room_id!("!admin_subspace:example.org");
1243 let regular_space_id = room_id!("!regular_space:example.org");
1244 let regular_subspace_id = room_id!("!regular_subspace:example.org");
1245
1246 add_space_rooms(
1247 vec![
1248 MockSpaceRoomParameters {
1249 room_id: admin_space_id,
1250 order: None,
1251 parents: vec![],
1252 children: vec![regular_subspace_id],
1253 power_level: Some(100),
1254 },
1255 MockSpaceRoomParameters {
1256 room_id: admin_subspace_id,
1257 order: None,
1258 parents: vec![regular_space_id],
1259 children: vec![],
1260 power_level: Some(100),
1261 },
1262 MockSpaceRoomParameters {
1263 room_id: regular_space_id,
1264 order: None,
1265 parents: vec![],
1266 children: vec![admin_subspace_id],
1267 power_level: Some(0),
1268 },
1269 MockSpaceRoomParameters {
1270 room_id: regular_subspace_id,
1271 order: None,
1272 parents: vec![admin_space_id],
1273 children: vec![],
1274 power_level: Some(0),
1275 },
1276 ],
1277 &client,
1278 &server,
1279 &factory,
1280 user_id,
1281 )
1282 .await;
1283
1284 let space_service = SpaceService::new(client.clone()).await;
1285
1286 let editable_spaces = space_service.editable_spaces().await;
1288
1289 assert_eq!(
1291 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
1292 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
1293 );
1294 }
1295
1296 #[async_test]
1297 async fn test_joined_parents_of_child() {
1298 let server = MatrixMockServer::new().await;
1300 let client = server.client_builder().build().await;
1301 let user_id = client.user_id().unwrap();
1302 let factory = EventFactory::new();
1303
1304 server.mock_room_state_encryption().plain().mount().await;
1305
1306 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1307 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1308 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1309 let child_space_id = room_id!("!child_space:example.org");
1310
1311 add_space_rooms(
1312 vec![
1313 MockSpaceRoomParameters {
1314 room_id: child_space_id,
1315 order: None,
1316 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1317 children: vec![],
1318 power_level: None,
1319 },
1320 MockSpaceRoomParameters {
1321 room_id: parent_space_id_1,
1322 order: None,
1323 parents: vec![],
1324 children: vec![child_space_id],
1325 power_level: None,
1326 },
1327 MockSpaceRoomParameters {
1328 room_id: parent_space_id_2,
1329 order: None,
1330 parents: vec![],
1331 children: vec![child_space_id],
1332 power_level: None,
1333 },
1334 ],
1335 &client,
1336 &server,
1337 &factory,
1338 user_id,
1339 )
1340 .await;
1341
1342 let space_service = SpaceService::new(client.clone()).await;
1343
1344 let parents = space_service.joined_parents_of_child(child_space_id).await;
1346
1347 assert_eq!(
1349 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
1350 vec![parent_space_id_1, parent_space_id_2]
1351 );
1352 }
1353
1354 #[async_test]
1355 async fn test_joined_parent_ids_of_child() {
1356 let server = MatrixMockServer::new().await;
1359 let client = server.client_builder().build().await;
1360 let user_id = client.user_id().unwrap();
1361 let factory = EventFactory::new();
1362
1363 server.mock_room_state_encryption().plain().mount().await;
1364
1365 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1366 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1367 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1368 let child_space_id = room_id!("!child_space:example.org");
1369 let child_room_id = room_id!("!child_room:example.org");
1370
1371 add_space_rooms(
1372 vec![
1373 MockSpaceRoomParameters {
1374 room_id: child_space_id,
1375 order: None,
1376 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1377 children: vec![child_room_id],
1378 power_level: None,
1379 },
1380 MockSpaceRoomParameters {
1381 room_id: parent_space_id_1,
1382 order: None,
1383 parents: vec![],
1384 children: vec![child_space_id],
1385 power_level: None,
1386 },
1387 MockSpaceRoomParameters {
1388 room_id: parent_space_id_2,
1389 order: None,
1390 parents: vec![],
1391 children: vec![child_space_id],
1392 power_level: None,
1393 },
1394 ],
1395 &client,
1396 &server,
1397 &factory,
1398 user_id,
1399 )
1400 .await;
1401
1402 let space_service = SpaceService::new(client.clone()).await;
1403
1404 let parent_ids = space_service.joined_parent_ids_of_child(child_space_id).await;
1406
1407 assert_eq!(parent_ids, vec![parent_space_id_1.to_owned(), parent_space_id_2.to_owned()]);
1412
1413 assert_eq!(
1416 space_service
1417 .joined_parents_of_child(child_space_id)
1418 .await
1419 .into_iter()
1420 .map(|space| space.room_id)
1421 .collect::<Vec<_>>(),
1422 parent_ids
1423 );
1424
1425 assert_eq!(
1428 space_service.joined_parent_ids_of_child(child_room_id).await,
1429 vec![child_space_id.to_owned()]
1430 );
1431
1432 assert!(space_service.joined_parent_ids_of_child(parent_space_id_1).await.is_empty());
1434
1435 assert!(
1437 space_service
1438 .joined_parent_ids_of_child(room_id!("!unknown_room:example.org"))
1439 .await
1440 .is_empty()
1441 );
1442 }
1443
1444 #[async_test]
1445 async fn test_top_level_ancestors_of() {
1446 let server = MatrixMockServer::new().await;
1449 let client = server.client_builder().build().await;
1450 let user_id = client.user_id().unwrap();
1451 let factory = EventFactory::new();
1452
1453 server.mock_room_state_encryption().plain().mount().await;
1454
1455 let top_level_space_id_1 = room_id!("!top_level_space_1:example.org");
1456 let top_level_space_id_2 = room_id!("!top_level_space_2:example.org");
1457 let middle_space_id = room_id!("!middle_space:example.org");
1458 let leaf_room_id = room_id!("!leaf_room:example.org");
1459
1460 add_space_rooms(
1461 vec![
1462 MockSpaceRoomParameters {
1463 room_id: top_level_space_id_1,
1464 order: None,
1465 parents: vec![],
1466 children: vec![middle_space_id],
1467 power_level: None,
1468 },
1469 MockSpaceRoomParameters {
1470 room_id: top_level_space_id_2,
1471 order: None,
1472 parents: vec![],
1473 children: vec![middle_space_id],
1474 power_level: None,
1475 },
1476 MockSpaceRoomParameters {
1477 room_id: middle_space_id,
1478 order: None,
1479 parents: vec![top_level_space_id_1, top_level_space_id_2],
1480 children: vec![leaf_room_id],
1481 power_level: None,
1482 },
1483 ],
1484 &client,
1485 &server,
1486 &factory,
1487 user_id,
1488 )
1489 .await;
1490
1491 let space_service = SpaceService::new(client.clone()).await;
1492
1493 assert_eq!(
1495 space_service.top_level_ancestors_of(leaf_room_id).await,
1496 HashSet::from([top_level_space_id_1.to_owned(), top_level_space_id_2.to_owned()])
1497 );
1498
1499 assert_eq!(
1501 space_service.top_level_ancestors_of(middle_space_id).await,
1502 HashSet::from([top_level_space_id_1.to_owned(), top_level_space_id_2.to_owned()])
1503 );
1504
1505 assert_eq!(
1507 space_service.top_level_ancestors_of(top_level_space_id_1).await,
1508 HashSet::from([top_level_space_id_1.to_owned()])
1509 );
1510
1511 assert!(
1514 space_service
1515 .top_level_ancestors_of(room_id!("!unknown_room:example.org"))
1516 .await
1517 .is_empty()
1518 );
1519 }
1520
1521 #[async_test]
1522 async fn test_top_level_ancestors_of_cyclic_spaces() {
1523 let server = MatrixMockServer::new().await;
1526 let client = server.client_builder().build().await;
1527 let user_id = client.user_id().unwrap();
1528 let factory = EventFactory::new();
1529
1530 server.mock_room_state_encryption().plain().mount().await;
1531
1532 let space_id_1 = room_id!("!cycle_space_1:example.org");
1533 let space_id_2 = room_id!("!cycle_space_2:example.org");
1534
1535 add_space_rooms(
1536 vec![
1537 MockSpaceRoomParameters {
1538 room_id: space_id_1,
1539 order: None,
1540 parents: vec![],
1541 children: vec![space_id_2],
1542 power_level: None,
1543 },
1544 MockSpaceRoomParameters {
1545 room_id: space_id_2,
1546 order: None,
1547 parents: vec![],
1548 children: vec![space_id_1],
1549 power_level: None,
1550 },
1551 ],
1552 &client,
1553 &server,
1554 &factory,
1555 user_id,
1556 )
1557 .await;
1558
1559 let space_service = SpaceService::new(client.clone()).await;
1560
1561 let ancestors_of_1 = space_service.top_level_ancestors_of(space_id_1).await;
1567 let ancestors_of_2 = space_service.top_level_ancestors_of(space_id_2).await;
1568
1569 assert_eq!(ancestors_of_1.len(), 1);
1570 assert_eq!(ancestors_of_1, ancestors_of_2);
1571 let root = ancestors_of_1.iter().next().unwrap();
1572 assert!([space_id_1, space_id_2].contains(&&**root));
1573 }
1574
1575 #[async_test]
1576 async fn test_get_space_room_for_id() {
1577 let server = MatrixMockServer::new().await;
1578 let client = server.client_builder().build().await;
1579 let user_id = client.user_id().unwrap();
1580 let factory = EventFactory::new();
1581
1582 server.mock_room_state_encryption().plain().mount().await;
1583
1584 let space_id = room_id!("!single_space:example.org");
1585
1586 add_space_rooms(
1587 vec![MockSpaceRoomParameters {
1588 room_id: space_id,
1589 order: None,
1590 parents: vec![],
1591 children: vec![],
1592 power_level: None,
1593 }],
1594 &client,
1595 &server,
1596 &factory,
1597 user_id,
1598 )
1599 .await;
1600
1601 let space_service = SpaceService::new(client.clone()).await;
1602
1603 let found = space_service.get_space_room(space_id).await;
1604 assert!(found.is_some());
1605
1606 let expected = SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await;
1607 assert_eq!(found.unwrap(), expected);
1608 }
1609
1610 #[async_test]
1611 async fn test_add_child_to_space() {
1612 let server = MatrixMockServer::new().await;
1614 let client = server.client_builder().build().await;
1615 let user_id = client.user_id().unwrap();
1616 let factory = EventFactory::new();
1617
1618 server.mock_room_state_encryption().plain().mount().await;
1619
1620 let space_child_event_id = event_id!("$1");
1621 let space_parent_event_id = event_id!("$2");
1622 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1623 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1624
1625 let space_id = room_id!("!my_space:example.org");
1626 let child_id = room_id!("!my_child:example.org");
1627
1628 add_space_rooms(
1629 vec![
1630 MockSpaceRoomParameters {
1631 room_id: space_id,
1632 order: None,
1633 parents: vec![],
1634 children: vec![],
1635 power_level: Some(100),
1636 },
1637 MockSpaceRoomParameters {
1638 room_id: child_id,
1639 order: None,
1640 parents: vec![],
1641 children: vec![],
1642 power_level: Some(100),
1643 },
1644 ],
1645 &client,
1646 &server,
1647 &factory,
1648 user_id,
1649 )
1650 .await;
1651
1652 let space_service = SpaceService::new(client.clone()).await;
1653
1654 let result =
1656 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1657
1658 assert!(result.is_ok());
1660 }
1661
1662 #[async_test]
1663 async fn test_add_child_to_space_without_space_admin() {
1664 let server = MatrixMockServer::new().await;
1666 let client = server.client_builder().build().await;
1667 let user_id = client.user_id().unwrap();
1668 let factory = EventFactory::new();
1669
1670 server.mock_room_state_encryption().plain().mount().await;
1671
1672 server.mock_set_space_child().unauthorized().expect(1).mount().await;
1673 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1674
1675 let space_id = room_id!("!my_space:example.org");
1676 let child_id = room_id!("!my_child:example.org");
1677
1678 add_space_rooms(
1679 vec![
1680 MockSpaceRoomParameters {
1681 room_id: space_id,
1682 order: None,
1683 parents: vec![],
1684 children: vec![],
1685 power_level: Some(0),
1686 },
1687 MockSpaceRoomParameters {
1688 room_id: child_id,
1689 order: None,
1690 parents: vec![],
1691 children: vec![],
1692 power_level: Some(0),
1693 },
1694 ],
1695 &client,
1696 &server,
1697 &factory,
1698 user_id,
1699 )
1700 .await;
1701
1702 let space_service = SpaceService::new(client.clone()).await;
1703
1704 let result =
1706 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1707
1708 assert!(result.is_err());
1711 }
1712
1713 #[async_test]
1714 async fn test_add_child_to_space_without_child_admin() {
1715 let server = MatrixMockServer::new().await;
1718 let client = server.client_builder().build().await;
1719 let user_id = client.user_id().unwrap();
1720 let factory = EventFactory::new();
1721
1722 server.mock_room_state_encryption().plain().mount().await;
1723
1724 let space_child_event_id = event_id!("$1");
1725 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1726 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1727
1728 let space_id = room_id!("!my_space:example.org");
1729 let child_id = room_id!("!my_child:example.org");
1730
1731 add_space_rooms(
1732 vec![
1733 MockSpaceRoomParameters {
1734 room_id: space_id,
1735 order: None,
1736 parents: vec![],
1737 children: vec![],
1738 power_level: Some(100),
1739 },
1740 MockSpaceRoomParameters {
1741 room_id: child_id,
1742 order: None,
1743 parents: vec![],
1744 children: vec![],
1745 power_level: Some(0),
1746 },
1747 ],
1748 &client,
1749 &server,
1750 &factory,
1751 user_id,
1752 )
1753 .await;
1754
1755 let space_service = SpaceService::new(client.clone()).await;
1756
1757 let result =
1759 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1760
1761 error!("result: {:?}", result);
1762 assert!(result.is_ok());
1765 }
1766
1767 #[async_test]
1768 async fn test_remove_child_from_space() {
1769 let server = MatrixMockServer::new().await;
1771 let client = server.client_builder().build().await;
1772 let user_id = client.user_id().unwrap();
1773 let factory = EventFactory::new();
1774
1775 server.mock_room_state_encryption().plain().mount().await;
1776
1777 let space_child_event_id = event_id!("$1");
1778 let space_parent_event_id = event_id!("$2");
1779 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1780 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1781
1782 let parent_id = room_id!("!parent_space:example.org");
1783 let child_id = room_id!("!child_space:example.org");
1784
1785 add_space_rooms(
1786 vec![
1787 MockSpaceRoomParameters {
1788 room_id: parent_id,
1789 order: None,
1790 parents: vec![],
1791 children: vec![child_id],
1792 power_level: None,
1793 },
1794 MockSpaceRoomParameters {
1795 room_id: child_id,
1796 order: None,
1797 parents: vec![parent_id],
1798 children: vec![],
1799 power_level: None,
1800 },
1801 ],
1802 &client,
1803 &server,
1804 &factory,
1805 user_id,
1806 )
1807 .await;
1808
1809 let space_service = SpaceService::new(client.clone()).await;
1810
1811 let result =
1813 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1814
1815 assert!(result.is_ok());
1817 }
1818
1819 #[async_test]
1820 async fn test_remove_child_from_space_without_parent_event() {
1821 let server = MatrixMockServer::new().await;
1823 let client = server.client_builder().build().await;
1824 let user_id = client.user_id().unwrap();
1825 let factory = EventFactory::new();
1826
1827 server.mock_room_state_encryption().plain().mount().await;
1828
1829 let space_child_event_id = event_id!("$1");
1830 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1831 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1832
1833 let parent_id = room_id!("!parent_space:example.org");
1834 let child_id = room_id!("!child_space:example.org");
1835
1836 add_space_rooms(
1837 vec![
1838 MockSpaceRoomParameters {
1839 room_id: parent_id,
1840 order: None,
1841 parents: vec![],
1842 children: vec![child_id],
1843 power_level: None,
1844 },
1845 MockSpaceRoomParameters {
1846 room_id: child_id,
1847 order: None,
1848 parents: vec![],
1849 children: vec![],
1850 power_level: None,
1851 },
1852 ],
1853 &client,
1854 &server,
1855 &factory,
1856 user_id,
1857 )
1858 .await;
1859
1860 let space_service = SpaceService::new(client.clone()).await;
1861
1862 let result =
1864 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1865
1866 assert!(result.is_ok());
1869 }
1870
1871 #[async_test]
1872 async fn test_remove_child_from_space_without_child_event() {
1873 let server = MatrixMockServer::new().await;
1875 let client = server.client_builder().build().await;
1876 let user_id = client.user_id().unwrap();
1877 let factory = EventFactory::new();
1878
1879 server.mock_room_state_encryption().plain().mount().await;
1880
1881 let space_parent_event_id = event_id!("$2");
1882 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1883 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1884
1885 let parent_id = room_id!("!parent_space:example.org");
1886 let child_id = room_id!("!child_space:example.org");
1887
1888 add_space_rooms(
1889 vec![
1890 MockSpaceRoomParameters {
1891 room_id: parent_id,
1892 order: None,
1893 parents: vec![],
1894 children: vec![],
1895 power_level: None,
1896 },
1897 MockSpaceRoomParameters {
1898 room_id: child_id,
1899 order: None,
1900 parents: vec![parent_id],
1901 children: vec![],
1902 power_level: None,
1903 },
1904 ],
1905 &client,
1906 &server,
1907 &factory,
1908 user_id,
1909 )
1910 .await;
1911
1912 let space_service = SpaceService::new(client.clone()).await;
1913
1914 let result =
1916 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1917
1918 assert!(result.is_ok());
1921 }
1922
1923 #[async_test]
1924 async fn test_remove_unknown_child_from_space() {
1925 let server = MatrixMockServer::new().await;
1927 let client = server.client_builder().build().await;
1928 let user_id = client.user_id().unwrap();
1929 let factory = EventFactory::new();
1930
1931 server.mock_room_state_encryption().plain().mount().await;
1932
1933 let space_child_event_id = event_id!("$1");
1934 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1935 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1937
1938 let parent_id = room_id!("!parent_space:example.org");
1939 let unknown_child_id = room_id!("!unknown_child:example.org");
1940
1941 add_space_rooms(
1943 vec![MockSpaceRoomParameters {
1944 room_id: parent_id,
1945 order: None,
1946 parents: vec![],
1947 children: vec![unknown_child_id],
1948 power_level: None,
1949 }],
1950 &client,
1951 &server,
1952 &factory,
1953 user_id,
1954 )
1955 .await;
1956
1957 assert!(client.get_room(unknown_child_id).is_none());
1959
1960 let space_service = SpaceService::new(client.clone()).await;
1961
1962 let result = space_service
1964 .remove_child_from_space(unknown_child_id.to_owned(), parent_id.to_owned())
1965 .await;
1966
1967 assert!(result.is_ok());
1970 }
1971
1972 #[async_test]
1973 async fn test_space_child_updates() {
1974 let server = MatrixMockServer::new().await;
1976 let client = server.client_builder().build().await;
1977 let user_id = client.user_id().unwrap();
1978 let factory = EventFactory::new();
1979
1980 server.mock_room_state_encryption().plain().mount().await;
1981
1982 let space_id = room_id!("!space:localhost");
1983 let first_child_id = room_id!("!first_child:localhost");
1984 let second_child_id = room_id!("!second_child:localhost");
1985
1986 server
1988 .sync_room(
1989 &client,
1990 JoinedRoomBuilder::new(space_id)
1991 .add_state_event(factory.create(user_id, RoomVersionId::V11).with_space_type()),
1992 )
1993 .await;
1994
1995 let space_service = SpaceService::new(client.clone()).await;
1998
1999 let (initial_values, joined_spaces_subscriber) =
2000 space_service.subscribe_to_top_level_joined_spaces().await;
2001 pin_mut!(joined_spaces_subscriber);
2002 assert_pending!(joined_spaces_subscriber);
2003
2004 assert_eq!(
2005 initial_values,
2006 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await].into()
2007 );
2008
2009 assert_eq!(
2010 space_service.top_level_joined_spaces().await,
2011 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await]
2012 );
2013
2014 server
2016 .sync_room(
2017 &client,
2018 JoinedRoomBuilder::new(space_id)
2019 .add_state_event(
2020 factory
2021 .space_child(space_id.to_owned(), first_child_id.to_owned())
2022 .sender(user_id),
2023 )
2024 .add_state_event(
2025 factory
2026 .space_child(space_id.to_owned(), second_child_id.to_owned())
2027 .sender(user_id),
2028 ),
2029 )
2030 .await;
2031
2032 assert_eq!(
2034 space_service.top_level_joined_spaces().await,
2035 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await]
2036 );
2037 assert_next_eq!(
2038 joined_spaces_subscriber,
2039 vec![
2040 VectorDiff::Clear,
2041 VectorDiff::Append {
2042 values: vec![
2043 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await
2044 ]
2045 .into()
2046 },
2047 ]
2048 );
2049
2050 server
2052 .sync_room(
2053 &client,
2054 JoinedRoomBuilder::new(space_id).add_state_bulk([Raw::new(&json!({
2055 "content": {},
2056 "type": "m.space.child",
2057 "event_id": "$cancelsecondchild",
2058 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
2059 "sender": user_id,
2060 "state_key": second_child_id,
2061 }))
2062 .unwrap()
2063 .cast_unchecked()]),
2064 )
2065 .await;
2066
2067 assert_eq!(
2069 space_service.top_level_joined_spaces().await,
2070 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await]
2071 );
2072 assert_next_eq!(
2073 joined_spaces_subscriber,
2074 vec![
2075 VectorDiff::Clear,
2076 VectorDiff::Append {
2077 values: vec![
2078 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await
2079 ]
2080 .into()
2081 },
2082 ]
2083 );
2084 }
2085
2086 async fn add_space_rooms(
2087 rooms: Vec<MockSpaceRoomParameters>,
2088 client: &Client,
2089 server: &MatrixMockServer,
2090 factory: &EventFactory,
2091 user_id: &UserId,
2092 ) {
2093 for parameters in rooms {
2094 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
2095 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
2096
2097 if let Some(order) = parameters.order {
2098 builder = builder.add_account_data(factory.space_order(order));
2099 }
2100
2101 for parent_id in parameters.parents {
2102 builder = builder.add_state_event(
2103 factory
2104 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
2105 .sender(user_id),
2106 );
2107 }
2108
2109 for child_id in parameters.children {
2110 builder = builder.add_state_event(
2111 factory
2112 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
2113 .sender(user_id),
2114 );
2115 }
2116
2117 let mut power_levels = if let Some(power_level) = parameters.power_level {
2118 BTreeMap::from([(user_id.to_owned(), power_level.into())])
2119 } else {
2120 BTreeMap::from([(user_id.to_owned(), 100.into())])
2121 };
2122
2123 builder = builder.add_state_event(
2124 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
2125 );
2126
2127 server.sync_room(client, builder).await;
2128 }
2129 }
2130
2131 struct MockSpaceRoomParameters {
2132 room_id: &'static RoomId,
2133 order: Option<&'static str>,
2134 parents: Vec<&'static RoomId>,
2135 children: Vec<&'static RoomId>,
2136 power_level: Option<i32>,
2137 }
2138
2139 fn any_room_id_and_space_room_order()
2140 -> impl Strategy<Value = (OwnedRoomId, Option<OwnedSpaceChildOrder>)> {
2141 let room_id = "[a-zA-Z]{1,5}".prop_map(|r| {
2142 RoomId::new_v2(&r).expect("Any string starting with ! should be a valid room ID")
2143 });
2144
2145 let order = prop::option::of("[a-zA-Z]{1,5}").prop_map(|order| {
2146 order.map(|o| SpaceChildOrder::parse(o).expect("Any string should be a valid order"))
2147 });
2148
2149 (room_id, order)
2150 }
2151
2152 proptest! {
2153 #[test]
2154 fn sort_top_level_space_room_never_panics(mut v in prop::collection::vec(any_room_id_and_space_room_order(), 0..100)) {
2155 v.sort_by(|a, b| {
2156 let (a_room_id, a_order) = a;
2157 let (b_room_id, b_order) = b;
2158
2159 let a = (a_room_id.as_ref(), a_order.as_deref());
2160 let b = (b_room_id.as_ref(), b_order.as_deref());
2161
2162 compare_top_level_space_rooms(a, b)
2163 })
2164 }
2165
2166 #[test]
2167 fn test_compare_top_level_rooms_reflexive(a in any_room_id_and_space_room_order()) {
2168 let (a_room_id, a_order) = a;
2169 let a = (a_room_id.as_ref(), a_order.as_deref());
2170
2171 prop_assert_eq!(compare_top_level_space_rooms(a, a), Ordering::Equal);
2172 }
2173
2174 #[test]
2175 fn test_compare_top_level_rooms_antisymmetric(a in any_room_id_and_space_room_order(), b in any_room_id_and_space_room_order()) {
2176 let (a_room_id, a_order) = a;
2177 let (b_room_id, b_order) = b;
2178
2179 let a = (a_room_id.as_ref(), a_order.as_deref());
2180 let b = (b_room_id.as_ref(), b_order.as_deref());
2181
2182 let ab = compare_top_level_space_rooms(a, b);
2183 let ba = compare_top_level_space_rooms(b, a);
2184
2185 prop_assert_eq!(ab, ba.reverse());
2186 }
2187
2188 #[test]
2189 fn test_compare_top_level_rooms_transitive(
2190 a in any_room_id_and_space_room_order(),
2191 b in any_room_id_and_space_room_order(),
2192 c in any_room_id_and_space_room_order()
2193 ) {
2194 let (a_room_id, a_order) = a;
2195 let (b_room_id, b_order) = b;
2196 let (c_room_id, c_order) = c;
2197
2198 let a = (a_room_id.as_ref(), a_order.as_deref());
2199 let b = (b_room_id.as_ref(), b_order.as_deref());
2200 let c = (c_room_id.as_ref(), c_order.as_deref());
2201
2202 let ab = compare_top_level_space_rooms(a, b);
2203 let bc = compare_top_level_space_rooms(b, c);
2204 let ac = compare_top_level_space_rooms(a, c);
2205
2206 if ab == Ordering::Less && bc == Ordering::Less {
2207 prop_assert_eq!(ac, Ordering::Less);
2208 }
2209
2210 if ab == Ordering::Equal && bc == Ordering::Equal {
2211 prop_assert_eq!(ac, Ordering::Equal);
2212 }
2213
2214 if ab == Ordering::Greater && bc == Ordering::Greater {
2215 prop_assert_eq!(ac, Ordering::Greater);
2216 }
2217 }
2218 }
2219}