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;
200 Self::update_space_state_if_needed(
201 Vector::from(spaces),
202 Vector::from(filters),
203 graph,
204 &space_state,
205 )
206 .await;
207
208 Self { client, space_state, _room_update_handle: AsyncMutex::new(room_update_handle) }
209 }
210
211 pub async fn subscribe_to_top_level_joined_spaces(
214 &self,
215 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
216 self.space_state
217 .lock()
218 .await
219 .top_level_joined_spaces
220 .subscribe()
221 .into_values_and_batched_stream()
222 }
223
224 pub async fn top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
228 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
229
230 Self::update_space_state_if_needed(
231 Vector::from(top_level_joined_spaces.clone()),
232 Vector::from(filters),
233 graph,
234 &self.space_state,
235 )
236 .await;
237
238 top_level_joined_spaces
239 }
240
241 pub async fn space_filters(&self) -> Vec<SpaceFilter> {
286 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
287
288 Self::update_space_state_if_needed(
289 Vector::from(top_level_joined_spaces),
290 Vector::from(filters.clone()),
291 graph,
292 &self.space_state,
293 )
294 .await;
295
296 filters
297 }
298
299 pub async fn subscribe_to_space_filters(
301 &self,
302 ) -> (Vector<SpaceFilter>, VectorSubscriberBatchedStream<SpaceFilter>) {
303 self.space_state.lock().await.space_filters.subscribe().into_values_and_batched_stream()
304 }
305
306 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
312 let Some(user_id) = self.client.user_id() else {
313 return vec![];
314 };
315
316 let graph = &self.space_state.lock().await.graph;
317 let rooms = self.client.joined_space_rooms();
318
319 let mut editable_spaces = Vec::new();
320 for room in &rooms {
321 if let Ok(power_levels) = room.power_levels().await
322 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
323 {
324 let room_id = room.room_id();
325 editable_spaces.push(
326 SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64).await,
327 );
328 }
329 }
330
331 editable_spaces
332 }
333
334 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
336 SpaceRoomList::new(self.client.clone(), space_id).await
337 }
338
339 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
341 let graph = &self.space_state.lock().await.graph;
342
343 let rooms = graph
344 .parents_of(child_id)
345 .into_iter()
346 .filter_map(|parent_id| self.client.get_room(parent_id));
347
348 join_all(rooms.map(|room| async move {
349 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64).await
350 }))
351 .await
352 }
353
354 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 eyeball_im::VectorDiff;
792 use futures_util::{StreamExt, pin_mut};
793 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
794 use matrix_sdk_test::{
795 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
796 };
797 use proptest::prelude::*;
798 use ruma::{
799 MilliSecondsSinceUnixEpoch, OwnedSpaceChildOrder, RoomVersionId, UserId, event_id,
800 owned_room_id, room_id, serde::Raw,
801 };
802 use serde_json::json;
803 use strass::assert_let;
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;
1236 let client = server.client_builder().build().await;
1237 let user_id = client.user_id().unwrap();
1238 let factory = EventFactory::new();
1239
1240 server.mock_room_state_encryption().plain().mount().await;
1241
1242 let admin_space_id = room_id!("!admin_space:example.org");
1243 let admin_subspace_id = room_id!("!admin_subspace:example.org");
1244 let regular_space_id = room_id!("!regular_space:example.org");
1245 let regular_subspace_id = room_id!("!regular_subspace:example.org");
1246
1247 add_space_rooms(
1248 vec![
1249 MockSpaceRoomParameters {
1250 room_id: admin_space_id,
1251 order: None,
1252 parents: vec![],
1253 children: vec![regular_subspace_id],
1254 power_level: Some(100),
1255 },
1256 MockSpaceRoomParameters {
1257 room_id: admin_subspace_id,
1258 order: None,
1259 parents: vec![regular_space_id],
1260 children: vec![],
1261 power_level: Some(100),
1262 },
1263 MockSpaceRoomParameters {
1264 room_id: regular_space_id,
1265 order: None,
1266 parents: vec![],
1267 children: vec![admin_subspace_id],
1268 power_level: Some(0),
1269 },
1270 MockSpaceRoomParameters {
1271 room_id: regular_subspace_id,
1272 order: None,
1273 parents: vec![admin_space_id],
1274 children: vec![],
1275 power_level: Some(0),
1276 },
1277 ],
1278 &client,
1279 &server,
1280 &factory,
1281 user_id,
1282 )
1283 .await;
1284
1285 let space_service = SpaceService::new(client.clone()).await;
1286
1287 let editable_spaces = space_service.editable_spaces().await;
1289
1290 assert_eq!(
1292 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
1293 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
1294 );
1295 }
1296
1297 #[async_test]
1298 async fn test_joined_parents_of_child() {
1299 let server = MatrixMockServer::new().await;
1301 let client = server.client_builder().build().await;
1302 let user_id = client.user_id().unwrap();
1303 let factory = EventFactory::new();
1304
1305 server.mock_room_state_encryption().plain().mount().await;
1306
1307 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1308 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1309 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1310 let child_space_id = room_id!("!child_space:example.org");
1311
1312 add_space_rooms(
1313 vec![
1314 MockSpaceRoomParameters {
1315 room_id: child_space_id,
1316 order: None,
1317 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1318 children: vec![],
1319 power_level: None,
1320 },
1321 MockSpaceRoomParameters {
1322 room_id: parent_space_id_1,
1323 order: None,
1324 parents: vec![],
1325 children: vec![child_space_id],
1326 power_level: None,
1327 },
1328 MockSpaceRoomParameters {
1329 room_id: parent_space_id_2,
1330 order: None,
1331 parents: vec![],
1332 children: vec![child_space_id],
1333 power_level: None,
1334 },
1335 ],
1336 &client,
1337 &server,
1338 &factory,
1339 user_id,
1340 )
1341 .await;
1342
1343 let space_service = SpaceService::new(client.clone()).await;
1344
1345 let parents = space_service.joined_parents_of_child(child_space_id).await;
1347
1348 assert_eq!(
1350 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
1351 vec![parent_space_id_1, parent_space_id_2]
1352 );
1353 }
1354
1355 #[async_test]
1356 async fn test_joined_parent_ids_of_child() {
1357 let server = MatrixMockServer::new().await;
1360 let client = server.client_builder().build().await;
1361 let user_id = client.user_id().unwrap();
1362 let factory = EventFactory::new();
1363
1364 server.mock_room_state_encryption().plain().mount().await;
1365
1366 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1367 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1368 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1369 let child_space_id = room_id!("!child_space:example.org");
1370 let child_room_id = room_id!("!child_room:example.org");
1371
1372 add_space_rooms(
1373 vec![
1374 MockSpaceRoomParameters {
1375 room_id: child_space_id,
1376 order: None,
1377 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1378 children: vec![child_room_id],
1379 power_level: None,
1380 },
1381 MockSpaceRoomParameters {
1382 room_id: parent_space_id_1,
1383 order: None,
1384 parents: vec![],
1385 children: vec![child_space_id],
1386 power_level: None,
1387 },
1388 MockSpaceRoomParameters {
1389 room_id: parent_space_id_2,
1390 order: None,
1391 parents: vec![],
1392 children: vec![child_space_id],
1393 power_level: None,
1394 },
1395 ],
1396 &client,
1397 &server,
1398 &factory,
1399 user_id,
1400 )
1401 .await;
1402
1403 let space_service = SpaceService::new(client.clone()).await;
1404
1405 let parent_ids = space_service.joined_parent_ids_of_child(child_space_id).await;
1407
1408 assert_eq!(parent_ids, vec![parent_space_id_1.to_owned(), parent_space_id_2.to_owned()]);
1413
1414 assert_eq!(
1417 space_service
1418 .joined_parents_of_child(child_space_id)
1419 .await
1420 .into_iter()
1421 .map(|space| space.room_id)
1422 .collect::<Vec<_>>(),
1423 parent_ids
1424 );
1425
1426 assert_eq!(
1429 space_service.joined_parent_ids_of_child(child_room_id).await,
1430 vec![child_space_id.to_owned()]
1431 );
1432
1433 assert!(space_service.joined_parent_ids_of_child(parent_space_id_1).await.is_empty());
1435
1436 assert!(
1438 space_service
1439 .joined_parent_ids_of_child(room_id!("!unknown_room:example.org"))
1440 .await
1441 .is_empty()
1442 );
1443 }
1444
1445 #[async_test]
1446 async fn test_top_level_ancestors_of() {
1447 let server = MatrixMockServer::new().await;
1450 let client = server.client_builder().build().await;
1451 let user_id = client.user_id().unwrap();
1452 let factory = EventFactory::new();
1453
1454 server.mock_room_state_encryption().plain().mount().await;
1455
1456 let top_level_space_id_1 = room_id!("!top_level_space_1:example.org");
1457 let top_level_space_id_2 = room_id!("!top_level_space_2:example.org");
1458 let middle_space_id = room_id!("!middle_space:example.org");
1459 let leaf_room_id = room_id!("!leaf_room:example.org");
1460
1461 add_space_rooms(
1462 vec![
1463 MockSpaceRoomParameters {
1464 room_id: top_level_space_id_1,
1465 order: None,
1466 parents: vec![],
1467 children: vec![middle_space_id],
1468 power_level: None,
1469 },
1470 MockSpaceRoomParameters {
1471 room_id: top_level_space_id_2,
1472 order: None,
1473 parents: vec![],
1474 children: vec![middle_space_id],
1475 power_level: None,
1476 },
1477 MockSpaceRoomParameters {
1478 room_id: middle_space_id,
1479 order: None,
1480 parents: vec![top_level_space_id_1, top_level_space_id_2],
1481 children: vec![leaf_room_id],
1482 power_level: None,
1483 },
1484 ],
1485 &client,
1486 &server,
1487 &factory,
1488 user_id,
1489 )
1490 .await;
1491
1492 let space_service = SpaceService::new(client.clone()).await;
1493
1494 assert_eq!(
1496 space_service.top_level_ancestors_of(leaf_room_id).await,
1497 HashSet::from([top_level_space_id_1.to_owned(), top_level_space_id_2.to_owned()])
1498 );
1499
1500 assert_eq!(
1502 space_service.top_level_ancestors_of(middle_space_id).await,
1503 HashSet::from([top_level_space_id_1.to_owned(), top_level_space_id_2.to_owned()])
1504 );
1505
1506 assert_eq!(
1508 space_service.top_level_ancestors_of(top_level_space_id_1).await,
1509 HashSet::from([top_level_space_id_1.to_owned()])
1510 );
1511
1512 assert!(
1515 space_service
1516 .top_level_ancestors_of(room_id!("!unknown_room:example.org"))
1517 .await
1518 .is_empty()
1519 );
1520 }
1521
1522 #[async_test]
1523 async fn test_top_level_ancestors_of_cyclic_spaces() {
1524 let server = MatrixMockServer::new().await;
1527 let client = server.client_builder().build().await;
1528 let user_id = client.user_id().unwrap();
1529 let factory = EventFactory::new();
1530
1531 server.mock_room_state_encryption().plain().mount().await;
1532
1533 let space_id_1 = room_id!("!cycle_space_1:example.org");
1534 let space_id_2 = room_id!("!cycle_space_2:example.org");
1535
1536 add_space_rooms(
1537 vec![
1538 MockSpaceRoomParameters {
1539 room_id: space_id_1,
1540 order: None,
1541 parents: vec![],
1542 children: vec![space_id_2],
1543 power_level: None,
1544 },
1545 MockSpaceRoomParameters {
1546 room_id: space_id_2,
1547 order: None,
1548 parents: vec![],
1549 children: vec![space_id_1],
1550 power_level: None,
1551 },
1552 ],
1553 &client,
1554 &server,
1555 &factory,
1556 user_id,
1557 )
1558 .await;
1559
1560 let space_service = SpaceService::new(client.clone()).await;
1561
1562 let ancestors_of_1 = space_service.top_level_ancestors_of(space_id_1).await;
1568 let ancestors_of_2 = space_service.top_level_ancestors_of(space_id_2).await;
1569
1570 assert_eq!(ancestors_of_1.len(), 1);
1571 assert_eq!(ancestors_of_1, ancestors_of_2);
1572 let root = ancestors_of_1.iter().next().unwrap();
1573 assert!([space_id_1, space_id_2].contains(&&**root));
1574 }
1575
1576 #[async_test]
1577 async fn test_get_space_room_for_id() {
1578 let server = MatrixMockServer::new().await;
1579 let client = server.client_builder().build().await;
1580 let user_id = client.user_id().unwrap();
1581 let factory = EventFactory::new();
1582
1583 server.mock_room_state_encryption().plain().mount().await;
1584
1585 let space_id = room_id!("!single_space:example.org");
1586
1587 add_space_rooms(
1588 vec![MockSpaceRoomParameters {
1589 room_id: space_id,
1590 order: None,
1591 parents: vec![],
1592 children: vec![],
1593 power_level: None,
1594 }],
1595 &client,
1596 &server,
1597 &factory,
1598 user_id,
1599 )
1600 .await;
1601
1602 let space_service = SpaceService::new(client.clone()).await;
1603
1604 let found = space_service.get_space_room(space_id).await;
1605 assert!(found.is_some());
1606
1607 let expected = SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await;
1608 assert_eq!(found.unwrap(), expected);
1609 }
1610
1611 #[async_test]
1612 async fn test_add_child_to_space() {
1613 let server = MatrixMockServer::new().await;
1615 let client = server.client_builder().build().await;
1616 let user_id = client.user_id().unwrap();
1617 let factory = EventFactory::new();
1618
1619 server.mock_room_state_encryption().plain().mount().await;
1620
1621 let space_child_event_id = event_id!("$1");
1622 let space_parent_event_id = event_id!("$2");
1623 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1624 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1625
1626 let space_id = room_id!("!my_space:example.org");
1627 let child_id = room_id!("!my_child:example.org");
1628
1629 add_space_rooms(
1630 vec![
1631 MockSpaceRoomParameters {
1632 room_id: space_id,
1633 order: None,
1634 parents: vec![],
1635 children: vec![],
1636 power_level: Some(100),
1637 },
1638 MockSpaceRoomParameters {
1639 room_id: child_id,
1640 order: None,
1641 parents: vec![],
1642 children: vec![],
1643 power_level: Some(100),
1644 },
1645 ],
1646 &client,
1647 &server,
1648 &factory,
1649 user_id,
1650 )
1651 .await;
1652
1653 let space_service = SpaceService::new(client.clone()).await;
1654
1655 let result =
1657 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1658
1659 assert!(result.is_ok());
1661 }
1662
1663 #[async_test]
1664 async fn test_add_child_to_space_without_space_admin() {
1665 let server = MatrixMockServer::new().await;
1668 let client = server.client_builder().build().await;
1669 let user_id = client.user_id().unwrap();
1670 let factory = EventFactory::new();
1671
1672 server.mock_room_state_encryption().plain().mount().await;
1673
1674 server.mock_set_space_child().unauthorized().expect(1).mount().await;
1675 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1676
1677 let space_id = room_id!("!my_space:example.org");
1678 let child_id = room_id!("!my_child:example.org");
1679
1680 add_space_rooms(
1681 vec![
1682 MockSpaceRoomParameters {
1683 room_id: space_id,
1684 order: None,
1685 parents: vec![],
1686 children: vec![],
1687 power_level: Some(0),
1688 },
1689 MockSpaceRoomParameters {
1690 room_id: child_id,
1691 order: None,
1692 parents: vec![],
1693 children: vec![],
1694 power_level: Some(0),
1695 },
1696 ],
1697 &client,
1698 &server,
1699 &factory,
1700 user_id,
1701 )
1702 .await;
1703
1704 let space_service = SpaceService::new(client.clone()).await;
1705
1706 let result =
1708 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1709
1710 assert!(result.is_err());
1713 }
1714
1715 #[async_test]
1716 async fn test_add_child_to_space_without_child_admin() {
1717 let server = MatrixMockServer::new().await;
1720 let client = server.client_builder().build().await;
1721 let user_id = client.user_id().unwrap();
1722 let factory = EventFactory::new();
1723
1724 server.mock_room_state_encryption().plain().mount().await;
1725
1726 let space_child_event_id = event_id!("$1");
1727 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1728 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1729
1730 let space_id = room_id!("!my_space:example.org");
1731 let child_id = room_id!("!my_child:example.org");
1732
1733 add_space_rooms(
1734 vec![
1735 MockSpaceRoomParameters {
1736 room_id: space_id,
1737 order: None,
1738 parents: vec![],
1739 children: vec![],
1740 power_level: Some(100),
1741 },
1742 MockSpaceRoomParameters {
1743 room_id: child_id,
1744 order: None,
1745 parents: vec![],
1746 children: vec![],
1747 power_level: Some(0),
1748 },
1749 ],
1750 &client,
1751 &server,
1752 &factory,
1753 user_id,
1754 )
1755 .await;
1756
1757 let space_service = SpaceService::new(client.clone()).await;
1758
1759 let result =
1761 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1762
1763 error!("result: {:?}", result);
1764 assert!(result.is_ok());
1767 }
1768
1769 #[async_test]
1770 async fn test_remove_child_from_space() {
1771 let server = MatrixMockServer::new().await;
1773 let client = server.client_builder().build().await;
1774 let user_id = client.user_id().unwrap();
1775 let factory = EventFactory::new();
1776
1777 server.mock_room_state_encryption().plain().mount().await;
1778
1779 let space_child_event_id = event_id!("$1");
1780 let space_parent_event_id = event_id!("$2");
1781 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1782 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1783
1784 let parent_id = room_id!("!parent_space:example.org");
1785 let child_id = room_id!("!child_space:example.org");
1786
1787 add_space_rooms(
1788 vec![
1789 MockSpaceRoomParameters {
1790 room_id: parent_id,
1791 order: None,
1792 parents: vec![],
1793 children: vec![child_id],
1794 power_level: None,
1795 },
1796 MockSpaceRoomParameters {
1797 room_id: child_id,
1798 order: None,
1799 parents: vec![parent_id],
1800 children: vec![],
1801 power_level: None,
1802 },
1803 ],
1804 &client,
1805 &server,
1806 &factory,
1807 user_id,
1808 )
1809 .await;
1810
1811 let space_service = SpaceService::new(client.clone()).await;
1812
1813 let result =
1815 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1816
1817 assert!(result.is_ok());
1819 }
1820
1821 #[async_test]
1822 async fn test_remove_child_from_space_without_parent_event() {
1823 let server = MatrixMockServer::new().await;
1825 let client = server.client_builder().build().await;
1826 let user_id = client.user_id().unwrap();
1827 let factory = EventFactory::new();
1828
1829 server.mock_room_state_encryption().plain().mount().await;
1830
1831 let space_child_event_id = event_id!("$1");
1832 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1833 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1834
1835 let parent_id = room_id!("!parent_space:example.org");
1836 let child_id = room_id!("!child_space:example.org");
1837
1838 add_space_rooms(
1839 vec![
1840 MockSpaceRoomParameters {
1841 room_id: parent_id,
1842 order: None,
1843 parents: vec![],
1844 children: vec![child_id],
1845 power_level: None,
1846 },
1847 MockSpaceRoomParameters {
1848 room_id: child_id,
1849 order: None,
1850 parents: vec![],
1851 children: vec![],
1852 power_level: None,
1853 },
1854 ],
1855 &client,
1856 &server,
1857 &factory,
1858 user_id,
1859 )
1860 .await;
1861
1862 let space_service = SpaceService::new(client.clone()).await;
1863
1864 let result =
1866 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1867
1868 assert!(result.is_ok());
1871 }
1872
1873 #[async_test]
1874 async fn test_remove_child_from_space_without_child_event() {
1875 let server = MatrixMockServer::new().await;
1878 let client = server.client_builder().build().await;
1879 let user_id = client.user_id().unwrap();
1880 let factory = EventFactory::new();
1881
1882 server.mock_room_state_encryption().plain().mount().await;
1883
1884 let space_parent_event_id = event_id!("$2");
1885 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1886 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1887
1888 let parent_id = room_id!("!parent_space:example.org");
1889 let child_id = room_id!("!child_space:example.org");
1890
1891 add_space_rooms(
1892 vec![
1893 MockSpaceRoomParameters {
1894 room_id: parent_id,
1895 order: None,
1896 parents: vec![],
1897 children: vec![],
1898 power_level: None,
1899 },
1900 MockSpaceRoomParameters {
1901 room_id: child_id,
1902 order: None,
1903 parents: vec![parent_id],
1904 children: vec![],
1905 power_level: None,
1906 },
1907 ],
1908 &client,
1909 &server,
1910 &factory,
1911 user_id,
1912 )
1913 .await;
1914
1915 let space_service = SpaceService::new(client.clone()).await;
1916
1917 let result =
1919 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1920
1921 assert!(result.is_ok());
1924 }
1925
1926 #[async_test]
1927 async fn test_remove_unknown_child_from_space() {
1928 let server = MatrixMockServer::new().await;
1931 let client = server.client_builder().build().await;
1932 let user_id = client.user_id().unwrap();
1933 let factory = EventFactory::new();
1934
1935 server.mock_room_state_encryption().plain().mount().await;
1936
1937 let space_child_event_id = event_id!("$1");
1938 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1939 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1942
1943 let parent_id = room_id!("!parent_space:example.org");
1944 let unknown_child_id = room_id!("!unknown_child:example.org");
1945
1946 add_space_rooms(
1948 vec![MockSpaceRoomParameters {
1949 room_id: parent_id,
1950 order: None,
1951 parents: vec![],
1952 children: vec![unknown_child_id],
1953 power_level: None,
1954 }],
1955 &client,
1956 &server,
1957 &factory,
1958 user_id,
1959 )
1960 .await;
1961
1962 assert!(client.get_room(unknown_child_id).is_none());
1964
1965 let space_service = SpaceService::new(client.clone()).await;
1966
1967 let result = space_service
1969 .remove_child_from_space(unknown_child_id.to_owned(), parent_id.to_owned())
1970 .await;
1971
1972 assert!(result.is_ok());
1976 }
1977
1978 #[async_test]
1979 async fn test_space_child_updates() {
1980 let server = MatrixMockServer::new().await;
1982 let client = server.client_builder().build().await;
1983 let user_id = client.user_id().unwrap();
1984 let factory = EventFactory::new();
1985
1986 server.mock_room_state_encryption().plain().mount().await;
1987
1988 let space_id = room_id!("!space:localhost");
1989 let first_child_id = room_id!("!first_child:localhost");
1990 let second_child_id = room_id!("!second_child:localhost");
1991
1992 server
1994 .sync_room(
1995 &client,
1996 JoinedRoomBuilder::new(space_id)
1997 .add_state_event(factory.create(user_id, RoomVersionId::V11).with_space_type()),
1998 )
1999 .await;
2000
2001 let space_service = SpaceService::new(client.clone()).await;
2004
2005 let (initial_values, joined_spaces_subscriber) =
2006 space_service.subscribe_to_top_level_joined_spaces().await;
2007 pin_mut!(joined_spaces_subscriber);
2008 assert_pending!(joined_spaces_subscriber);
2009
2010 assert_eq!(
2011 initial_values,
2012 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await].into()
2013 );
2014
2015 assert_eq!(
2016 space_service.top_level_joined_spaces().await,
2017 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await]
2018 );
2019
2020 server
2022 .sync_room(
2023 &client,
2024 JoinedRoomBuilder::new(space_id)
2025 .add_state_event(
2026 factory
2027 .space_child(space_id.to_owned(), first_child_id.to_owned())
2028 .sender(user_id),
2029 )
2030 .add_state_event(
2031 factory
2032 .space_child(space_id.to_owned(), second_child_id.to_owned())
2033 .sender(user_id),
2034 ),
2035 )
2036 .await;
2037
2038 assert_eq!(
2040 space_service.top_level_joined_spaces().await,
2041 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await]
2042 );
2043 assert_next_eq!(
2044 joined_spaces_subscriber,
2045 vec![
2046 VectorDiff::Clear,
2047 VectorDiff::Append {
2048 values: vec![
2049 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await
2050 ]
2051 .into()
2052 },
2053 ]
2054 );
2055
2056 server
2058 .sync_room(
2059 &client,
2060 JoinedRoomBuilder::new(space_id).add_state_bulk([Raw::new(&json!({
2061 "content": {},
2062 "type": "m.space.child",
2063 "event_id": "$cancelsecondchild",
2064 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
2065 "sender": user_id,
2066 "state_key": second_child_id,
2067 }))
2068 .unwrap()
2069 .cast_unchecked()]),
2070 )
2071 .await;
2072
2073 assert_eq!(
2075 space_service.top_level_joined_spaces().await,
2076 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await]
2077 );
2078 assert_next_eq!(
2079 joined_spaces_subscriber,
2080 vec![
2081 VectorDiff::Clear,
2082 VectorDiff::Append {
2083 values: vec![
2084 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await
2085 ]
2086 .into()
2087 },
2088 ]
2089 );
2090 }
2091
2092 async fn add_space_rooms(
2093 rooms: Vec<MockSpaceRoomParameters>,
2094 client: &Client,
2095 server: &MatrixMockServer,
2096 factory: &EventFactory,
2097 user_id: &UserId,
2098 ) {
2099 for parameters in rooms {
2100 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
2101 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
2102
2103 if let Some(order) = parameters.order {
2104 builder = builder.add_account_data(factory.space_order(order));
2105 }
2106
2107 for parent_id in parameters.parents {
2108 builder = builder.add_state_event(
2109 factory
2110 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
2111 .sender(user_id),
2112 );
2113 }
2114
2115 for child_id in parameters.children {
2116 builder = builder.add_state_event(
2117 factory
2118 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
2119 .sender(user_id),
2120 );
2121 }
2122
2123 let mut power_levels = if let Some(power_level) = parameters.power_level {
2124 BTreeMap::from([(user_id.to_owned(), power_level.into())])
2125 } else {
2126 BTreeMap::from([(user_id.to_owned(), 100.into())])
2127 };
2128
2129 builder = builder.add_state_event(
2130 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
2131 );
2132
2133 server.sync_room(client, builder).await;
2134 }
2135 }
2136
2137 struct MockSpaceRoomParameters {
2138 room_id: &'static RoomId,
2139 order: Option<&'static str>,
2140 parents: Vec<&'static RoomId>,
2141 children: Vec<&'static RoomId>,
2142 power_level: Option<i32>,
2143 }
2144
2145 fn any_room_id_and_space_room_order()
2146 -> impl Strategy<Value = (OwnedRoomId, Option<OwnedSpaceChildOrder>)> {
2147 let room_id = "[a-zA-Z]{1,5}".prop_map(|r| {
2148 RoomId::new_v2(&r).expect("Any string starting with ! should be a valid room ID")
2149 });
2150
2151 let order = prop::option::of("[a-zA-Z]{1,5}").prop_map(|order| {
2152 order.map(|o| SpaceChildOrder::parse(o).expect("Any string should be a valid order"))
2153 });
2154
2155 (room_id, order)
2156 }
2157
2158 proptest! {
2159 #[test]
2160 fn sort_top_level_space_room_never_panics(mut v in prop::collection::vec(any_room_id_and_space_room_order(), 0..100)) {
2161 v.sort_by(|a, b| {
2162 let (a_room_id, a_order) = a;
2163 let (b_room_id, b_order) = b;
2164
2165 let a = (a_room_id.as_ref(), a_order.as_deref());
2166 let b = (b_room_id.as_ref(), b_order.as_deref());
2167
2168 compare_top_level_space_rooms(a, b)
2169 })
2170 }
2171
2172 #[test]
2173 fn test_compare_top_level_rooms_reflexive(a in any_room_id_and_space_room_order()) {
2174 let (a_room_id, a_order) = a;
2175 let a = (a_room_id.as_ref(), a_order.as_deref());
2176
2177 prop_assert_eq!(compare_top_level_space_rooms(a, a), Ordering::Equal);
2178 }
2179
2180 #[test]
2181 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()) {
2182 let (a_room_id, a_order) = a;
2183 let (b_room_id, b_order) = b;
2184
2185 let a = (a_room_id.as_ref(), a_order.as_deref());
2186 let b = (b_room_id.as_ref(), b_order.as_deref());
2187
2188 let ab = compare_top_level_space_rooms(a, b);
2189 let ba = compare_top_level_space_rooms(b, a);
2190
2191 prop_assert_eq!(ab, ba.reverse());
2192 }
2193
2194 #[test]
2195 fn test_compare_top_level_rooms_transitive(
2196 a in any_room_id_and_space_room_order(),
2197 b in any_room_id_and_space_room_order(),
2198 c in any_room_id_and_space_room_order()
2199 ) {
2200 let (a_room_id, a_order) = a;
2201 let (b_room_id, b_order) = b;
2202 let (c_room_id, c_order) = c;
2203
2204 let a = (a_room_id.as_ref(), a_order.as_deref());
2205 let b = (b_room_id.as_ref(), b_order.as_deref());
2206 let c = (c_room_id.as_ref(), c_order.as_deref());
2207
2208 let ab = compare_top_level_space_rooms(a, b);
2209 let bc = compare_top_level_space_rooms(b, c);
2210 let ac = compare_top_level_space_rooms(a, c);
2211
2212 if ab == Ordering::Less && bc == Ordering::Less {
2213 prop_assert_eq!(ac, Ordering::Less);
2214 }
2215
2216 if ab == Ordering::Equal && bc == Ordering::Equal {
2217 prop_assert_eq!(ac, Ordering::Equal);
2218 }
2219
2220 if ab == Ordering::Greater && bc == Ordering::Greater {
2221 prop_assert_eq!(ac, Ordering::Greater);
2222 }
2223 }
2224 }
2225}