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