1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
31
32use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
33use futures_util::pin_mut;
34use imbl::Vector;
35use itertools::Itertools;
36use matrix_sdk::{
37 Client, Error as SDKError, deserialized_responses::SyncOrStrippedState, executor::AbortOnDrop,
38};
39use matrix_sdk_common::executor::spawn;
40use ruma::{
41 OwnedRoomId, RoomId,
42 events::{
43 self, StateEventType, SyncStateEvent,
44 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
45 },
46};
47use thiserror::Error;
48use tokio::sync::Mutex as AsyncMutex;
49use tracing::{error, warn};
50
51use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle};
52pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
53
54pub mod graph;
55pub mod leave;
56pub mod room;
57pub mod room_list;
58
59#[derive(Debug, Error)]
61pub enum Error {
62 #[error("User ID not available from client")]
64 UserIdNotFound,
65
66 #[error("Room `{0}` not found")]
68 RoomNotFound(OwnedRoomId),
69
70 #[error("Missing `{0}` for `{1}`")]
72 MissingState(StateEventType, OwnedRoomId),
73
74 #[error("Failed updating space parent/child relationship")]
76 UpdateRelationship(SDKError),
77
78 #[error("Failed to leave space")]
80 LeaveSpace(SDKError),
81
82 #[error("Failed to load members")]
84 LoadRoomMembers(SDKError),
85}
86
87struct SpaceState {
88 graph: SpaceGraph,
89 joined_rooms: ObservableVector<SpaceRoom>,
90}
91
92pub struct SpaceService {
132 client: Client,
133
134 space_state: Arc<AsyncMutex<SpaceState>>,
135
136 room_update_handle: AsyncMutex<Option<AbortOnDrop<()>>>,
137}
138
139impl SpaceService {
140 pub fn new(client: Client) -> Self {
142 Self {
143 client,
144 space_state: Arc::new(AsyncMutex::new(SpaceState {
145 graph: SpaceGraph::new(),
146 joined_rooms: ObservableVector::new(),
147 })),
148 room_update_handle: AsyncMutex::new(None),
149 }
150 }
151
152 pub async fn subscribe_to_joined_spaces(
155 &self,
156 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
157 let mut room_update_handle = self.room_update_handle.lock().await;
158
159 if room_update_handle.is_none() {
160 let client = self.client.clone();
161 let space_state = Arc::clone(&self.space_state);
162 let all_room_updates_receiver = self.client.subscribe_to_all_room_updates();
163
164 *room_update_handle = Some(AbortOnDrop::new(spawn(async move {
165 pin_mut!(all_room_updates_receiver);
166
167 loop {
168 match all_room_updates_receiver.recv().await {
169 Ok(updates) => {
170 if updates.is_empty() {
171 continue;
172 }
173
174 let (spaces, graph) = Self::joined_spaces_for(&client).await;
175 Self::update_joined_spaces_if_needed(
176 Vector::from(spaces),
177 graph,
178 &space_state,
179 )
180 .await;
181 }
182 Err(err) => {
183 error!("error when listening to room updates: {err}");
184 }
185 }
186 }
187 })));
188
189 let (spaces, graph) = Self::joined_spaces_for(&self.client).await;
191 Self::update_joined_spaces_if_needed(Vector::from(spaces), graph, &self.space_state)
192 .await;
193 }
194
195 self.space_state.lock().await.joined_rooms.subscribe().into_values_and_batched_stream()
196 }
197
198 pub async fn joined_spaces(&self) -> Vec<SpaceRoom> {
202 let (spaces, graph) = Self::joined_spaces_for(&self.client).await;
203
204 Self::update_joined_spaces_if_needed(
205 Vector::from(spaces.clone()),
206 graph,
207 &self.space_state,
208 )
209 .await;
210
211 spaces
212 }
213
214 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
220 let Some(user_id) = self.client.user_id() else {
221 return vec![];
222 };
223
224 let graph = &self.space_state.lock().await.graph;
225 let rooms = self.client.joined_space_rooms();
226
227 let mut editable_spaces = Vec::new();
228 for room in &rooms {
229 if let Ok(power_levels) = room.power_levels().await
230 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
231 {
232 let room_id = room.room_id();
233 editable_spaces
234 .push(SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64));
235 }
236 }
237
238 editable_spaces
239 }
240
241 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
243 SpaceRoomList::new(self.client.clone(), space_id).await
244 }
245
246 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
248 let graph = &self.space_state.lock().await.graph;
249
250 graph
251 .parents_of(child_id)
252 .into_iter()
253 .filter_map(|parent_id| self.client.get_room(parent_id))
254 .map(|room| {
255 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64)
256 })
257 .collect()
258 }
259
260 pub async fn add_child_to_space(
261 &self,
262 child_id: OwnedRoomId,
263 space_id: OwnedRoomId,
264 ) -> Result<(), Error> {
265 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
266 let space_room =
267 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
268 let child_room =
269 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
270 let child_power_levels = child_room
271 .power_levels()
272 .await
273 .map_err(|error| Error::UpdateRelationship(matrix_sdk::Error::from(error)))?;
274
275 let child_route = child_room.route().await.map_err(Error::UpdateRelationship)?;
277 space_room
278 .send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
279 .await
280 .map_err(Error::UpdateRelationship)?;
281
282 if child_power_levels.user_can_send_state(user_id, StateEventType::SpaceParent) {
284 let parent_route = space_room.route().await.map_err(Error::UpdateRelationship)?;
285 child_room
286 .send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
287 .await
288 .map_err(Error::UpdateRelationship)?;
289 } else {
290 warn!("The current user doesn't have permission to set the child's parent.");
291 }
292
293 Ok(())
294 }
295
296 pub async fn remove_child_from_space(
297 &self,
298 child_id: OwnedRoomId,
299 space_id: OwnedRoomId,
300 ) -> Result<(), Error> {
301 let space_room =
302 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
303 let child_room =
304 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
305
306 if let Ok(Some(_)) =
307 space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
308 {
309 space_room
316 .send_state_event_raw("m.space.child", child_id.as_str(), serde_json::json!({}))
317 .await
318 .map_err(Error::UpdateRelationship)?;
319 } else {
320 warn!("A space child event wasn't found on the parent, ignoring.");
321 }
322
323 if let Ok(Some(_)) =
324 child_room.get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id).await
325 {
326 child_room
328 .send_state_event_raw("m.space.parent", space_id.as_str(), serde_json::json!({}))
329 .await
330 .map_err(Error::UpdateRelationship)?;
331 } else {
332 warn!("A space parent event wasn't found on the child, ignoring.");
333 }
334
335 Ok(())
336 }
337
338 pub async fn leave_space(&self, space_id: &RoomId) -> Result<LeaveSpaceHandle, Error> {
346 let space_state = self.space_state.lock().await;
347
348 if !space_state.graph.has_node(space_id) {
349 return Err(Error::RoomNotFound(space_id.to_owned()));
350 }
351
352 let room_ids = space_state.graph.flattened_bottom_up_subtree(space_id);
353
354 let handle = LeaveSpaceHandle::new(self.client.clone(), room_ids).await;
355
356 Ok(handle)
357 }
358
359 async fn update_joined_spaces_if_needed(
360 new_spaces: Vector<SpaceRoom>,
361 new_graph: SpaceGraph,
362 space_state: &Arc<AsyncMutex<SpaceState>>,
363 ) {
364 let mut space_state = space_state.lock().await;
365
366 if new_spaces != space_state.joined_rooms.clone() {
367 space_state.joined_rooms.clear();
368 space_state.joined_rooms.append(new_spaces);
369 }
370
371 space_state.graph = new_graph;
372 }
373
374 async fn joined_spaces_for(client: &Client) -> (Vec<SpaceRoom>, SpaceGraph) {
375 let joined_spaces = client.joined_space_rooms();
376
377 let mut graph = SpaceGraph::new();
379
380 for space in joined_spaces.iter() {
383 graph.add_node(space.room_id().to_owned());
384
385 if let Ok(parents) = space.get_state_events_static::<SpaceParentEventContent>().await {
386 parents.into_iter()
387 .flat_map(|parent_event| match parent_event.deserialize() {
388 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
389 Some(e.state_key)
390 }
391 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
392 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
393 Err(e) => {
394 error!(room_id = ?space.room_id(), "Could not deserialize m.space.parent: {e}");
395 None
396 }
397 }).for_each(|parent| graph.add_edge(parent, space.room_id().to_owned()));
398 } else {
399 error!(room_id = ?space.room_id(), "Could not get m.space.parent events");
400 }
401
402 if let Ok(children) = space.get_state_events_static::<SpaceChildEventContent>().await {
403 children.into_iter()
404 .filter_map(|child_event| match child_event.deserialize() {
405 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
406 Some(e.state_key)
407 }
408 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
409 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
410 Err(e) => {
411 error!(room_id = ?space.room_id(), "Could not deserialize m.space.child: {e}");
412 None
413 }
414 }).for_each(|child| graph.add_edge(space.room_id().to_owned(), child));
415 } else {
416 error!(room_id = ?space.room_id(), "Could not get m.space.child events");
417 }
418 }
419
420 graph.remove_cycles();
423
424 let root_nodes = graph.root_nodes();
425
426 let top_level_spaces = joined_spaces
430 .iter()
431 .filter(|room| root_nodes.contains(&room.room_id()))
432 .collect::<Vec<_>>();
433
434 let mut top_level_space_order = HashMap::new();
435 for space in &top_level_spaces {
436 if let Ok(Some(raw_event)) =
437 space.account_data_static::<events::space_order::SpaceOrderEventContent>().await
438 && let Ok(event) = raw_event.deserialize()
439 {
440 top_level_space_order.insert(space.room_id().to_owned(), event.content.order);
441 }
442 }
443
444 let top_level_spaces = top_level_spaces
445 .iter()
446 .sorted_by(|a, b| {
447 match (
449 top_level_space_order.get(a.room_id()),
450 top_level_space_order.get(b.room_id()),
451 ) {
452 (Some(a_order), Some(b_order)) => {
453 a_order.cmp(b_order).then(a.room_id().cmp(b.room_id()))
454 }
455 (Some(_), None) => Ordering::Less,
456 (None, Some(_)) => Ordering::Greater,
457 (None, None) => a.room_id().cmp(b.room_id()),
458 }
459 })
460 .map(|room| {
461 SpaceRoom::new_from_known(room, graph.children_of(room.room_id()).len() as u64)
462 })
463 .collect();
464
465 (top_level_spaces, graph)
466 }
467}
468
469#[cfg(test)]
470mod tests {
471 use std::collections::BTreeMap;
472
473 use assert_matches2::assert_let;
474 use eyeball_im::VectorDiff;
475 use futures_util::{StreamExt, pin_mut};
476 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
477 use matrix_sdk_test::{
478 JoinedRoomBuilder, LeftRoomBuilder, RoomAccountDataTestEvent, async_test,
479 event_factory::EventFactory,
480 };
481 use ruma::{RoomVersionId, UserId, event_id, owned_room_id, room_id};
482 use serde_json::json;
483 use stream_assert::{assert_next_eq, assert_pending};
484
485 use super::*;
486
487 #[async_test]
488 async fn test_spaces_hierarchy() {
489 let server = MatrixMockServer::new().await;
490 let client = server.client_builder().build().await;
491 let user_id = client.user_id().unwrap();
492 let space_service = SpaceService::new(client.clone());
493 let factory = EventFactory::new();
494
495 server.mock_room_state_encryption().plain().mount().await;
496
497 let parent_space_id = room_id!("!parent_space:example.org");
500 let child_space_id_1 = room_id!("!child_space_1:example.org");
501 let child_space_id_2 = room_id!("!child_space_2:example.org");
502
503 add_space_rooms(
504 vec![
505 MockSpaceRoomParameters {
506 room_id: child_space_id_1,
507 order: None,
508 parents: vec![parent_space_id],
509 children: vec![],
510 power_level: None,
511 },
512 MockSpaceRoomParameters {
513 room_id: child_space_id_2,
514 order: None,
515 parents: vec![parent_space_id],
516 children: vec![],
517 power_level: None,
518 },
519 MockSpaceRoomParameters {
520 room_id: parent_space_id,
521 order: None,
522 parents: vec![],
523 children: vec![child_space_id_1, child_space_id_2],
524 power_level: None,
525 },
526 ],
527 &client,
528 &server,
529 &factory,
530 user_id,
531 )
532 .await;
533
534 assert_eq!(
536 space_service
537 .joined_spaces()
538 .await
539 .iter()
540 .map(|s| s.room_id.to_owned())
541 .collect::<Vec<_>>(),
542 vec![parent_space_id]
543 );
544
545 assert_eq!(
547 space_service
548 .joined_spaces()
549 .await
550 .iter()
551 .map(|s| s.children_count)
552 .collect::<Vec<_>>(),
553 vec![2]
554 );
555
556 let parent_space = client.get_room(parent_space_id).unwrap();
557 assert!(parent_space.is_space());
558
559 let spaces: Vec<ParentSpace> = client
562 .get_room(child_space_id_1)
563 .unwrap()
564 .parent_spaces()
565 .await
566 .unwrap()
567 .map(Result::unwrap)
568 .collect()
569 .await;
570
571 assert_let!(ParentSpace::Reciprocal(parent) = spaces.first().unwrap());
572 assert_eq!(parent.room_id(), parent_space.room_id());
573
574 let spaces: Vec<ParentSpace> = client
575 .get_room(child_space_id_2)
576 .unwrap()
577 .parent_spaces()
578 .await
579 .unwrap()
580 .map(Result::unwrap)
581 .collect()
582 .await;
583
584 assert_let!(ParentSpace::Reciprocal(parent) = spaces.last().unwrap());
585 assert_eq!(parent.room_id(), parent_space.room_id());
586 }
587
588 #[async_test]
589 async fn test_joined_spaces_updates() {
590 let server = MatrixMockServer::new().await;
591 let client = server.client_builder().build().await;
592 let user_id = client.user_id().unwrap();
593 let factory = EventFactory::new();
594
595 server.mock_room_state_encryption().plain().mount().await;
596
597 let first_space_id = room_id!("!first_space:example.org");
598 let second_space_id = room_id!("!second_space:example.org");
599
600 server
602 .sync_room(
603 &client,
604 JoinedRoomBuilder::new(first_space_id)
605 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type()),
606 )
607 .await;
608
609 let space_service = SpaceService::new(client.clone());
613
614 let (initial_values, joined_spaces_subscriber) =
615 space_service.subscribe_to_joined_spaces().await;
616 pin_mut!(joined_spaces_subscriber);
617 assert_pending!(joined_spaces_subscriber);
618
619 assert_eq!(
620 initial_values,
621 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)].into()
622 );
623
624 assert_eq!(
625 space_service.joined_spaces().await,
626 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
627 );
628
629 assert_pending!(joined_spaces_subscriber);
632
633 server
636 .sync_room(
637 &client,
638 JoinedRoomBuilder::new(second_space_id)
639 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
640 .add_state_event(
641 factory
642 .space_child(
643 second_space_id.to_owned(),
644 owned_room_id!("!child:example.org"),
645 )
646 .sender(user_id),
647 ),
648 )
649 .await;
650
651 assert_eq!(
653 space_service.joined_spaces().await,
654 vec![
655 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
656 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
657 ]
658 );
659
660 assert_next_eq!(
661 joined_spaces_subscriber,
662 vec![
663 VectorDiff::Clear,
664 VectorDiff::Append {
665 values: vec![
666 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
667 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
668 ]
669 .into()
670 },
671 ]
672 );
673
674 server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
675
676 assert_next_eq!(
678 joined_spaces_subscriber,
679 vec![
680 VectorDiff::Clear,
681 VectorDiff::Append {
682 values: vec![SpaceRoom::new_from_known(
683 &client.get_room(first_space_id).unwrap(),
684 0
685 )]
686 .into()
687 },
688 ]
689 );
690
691 server
693 .sync_room(
694 &client,
695 JoinedRoomBuilder::new(room_id!("!room:example.org"))
696 .add_state_event(factory.create(user_id, RoomVersionId::V1)),
697 )
698 .await;
699
700 assert_pending!(joined_spaces_subscriber);
702 assert_eq!(
703 space_service.joined_spaces().await,
704 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
705 );
706 }
707
708 #[async_test]
709 async fn test_top_level_space_order() {
710 let server = MatrixMockServer::new().await;
711 let client = server.client_builder().build().await;
712
713 server.mock_room_state_encryption().plain().mount().await;
714
715 add_space_rooms(
716 vec![
717 MockSpaceRoomParameters {
718 room_id: room_id!("!2:a.b"),
719 order: Some("2"),
720 parents: vec![],
721 children: vec![],
722 power_level: None,
723 },
724 MockSpaceRoomParameters {
725 room_id: room_id!("!4:a.b"),
726 order: None,
727 parents: vec![],
728 children: vec![],
729 power_level: None,
730 },
731 MockSpaceRoomParameters {
732 room_id: room_id!("!3:a.b"),
733 order: None,
734 parents: vec![],
735 children: vec![],
736 power_level: None,
737 },
738 MockSpaceRoomParameters {
739 room_id: room_id!("!1:a.b"),
740 order: Some("1"),
741 parents: vec![],
742 children: vec![],
743 power_level: None,
744 },
745 ],
746 &client,
747 &server,
748 &EventFactory::new(),
749 client.user_id().unwrap(),
750 )
751 .await;
752
753 let space_service = SpaceService::new(client.clone());
754
755 assert_eq!(
758 space_service.joined_spaces().await,
759 vec![
760 SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0),
761 SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0),
762 SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0),
763 SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0),
764 ]
765 );
766 }
767
768 #[async_test]
769 async fn test_editable_spaces() {
770 let server = MatrixMockServer::new().await;
772 let client = server.client_builder().build().await;
773 let user_id = client.user_id().unwrap();
774 let factory = EventFactory::new();
775
776 server.mock_room_state_encryption().plain().mount().await;
777
778 let admin_space_id = room_id!("!admin_space:example.org");
779 let admin_subspace_id = room_id!("!admin_subspace:example.org");
780 let regular_space_id = room_id!("!regular_space:example.org");
781 let regular_subspace_id = room_id!("!regular_subspace:example.org");
782
783 add_space_rooms(
784 vec![
785 MockSpaceRoomParameters {
786 room_id: admin_space_id,
787 order: None,
788 parents: vec![],
789 children: vec![regular_subspace_id],
790 power_level: Some(100),
791 },
792 MockSpaceRoomParameters {
793 room_id: admin_subspace_id,
794 order: None,
795 parents: vec![regular_space_id],
796 children: vec![],
797 power_level: Some(100),
798 },
799 MockSpaceRoomParameters {
800 room_id: regular_space_id,
801 order: None,
802 parents: vec![],
803 children: vec![admin_subspace_id],
804 power_level: Some(0),
805 },
806 MockSpaceRoomParameters {
807 room_id: regular_subspace_id,
808 order: None,
809 parents: vec![admin_space_id],
810 children: vec![],
811 power_level: Some(0),
812 },
813 ],
814 &client,
815 &server,
816 &factory,
817 user_id,
818 )
819 .await;
820
821 let space_service = SpaceService::new(client.clone());
822
823 _ = space_service.joined_spaces().await;
825
826 let editable_spaces = space_service.editable_spaces().await;
828
829 assert_eq!(
831 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
832 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
833 );
834 }
835
836 #[async_test]
837 async fn test_joined_parents_of_child() {
838 let server = MatrixMockServer::new().await;
840 let client = server.client_builder().build().await;
841 let user_id = client.user_id().unwrap();
842 let factory = EventFactory::new();
843
844 server.mock_room_state_encryption().plain().mount().await;
845
846 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
847 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
848 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
849 let child_space_id = room_id!("!child_space:example.org");
850
851 add_space_rooms(
852 vec![
853 MockSpaceRoomParameters {
854 room_id: child_space_id,
855 order: None,
856 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
857 children: vec![],
858 power_level: None,
859 },
860 MockSpaceRoomParameters {
861 room_id: parent_space_id_1,
862 order: None,
863 parents: vec![],
864 children: vec![child_space_id],
865 power_level: None,
866 },
867 MockSpaceRoomParameters {
868 room_id: parent_space_id_2,
869 order: None,
870 parents: vec![],
871 children: vec![child_space_id],
872 power_level: None,
873 },
874 ],
875 &client,
876 &server,
877 &factory,
878 user_id,
879 )
880 .await;
881
882 let space_service = SpaceService::new(client.clone());
883
884 _ = space_service.joined_spaces().await;
886
887 let parents = space_service.joined_parents_of_child(child_space_id).await;
889
890 assert_eq!(
892 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
893 vec![parent_space_id_1, parent_space_id_2]
894 );
895 }
896
897 #[async_test]
898 async fn test_add_child_to_space() {
899 let server = MatrixMockServer::new().await;
901 let client = server.client_builder().build().await;
902 let user_id = client.user_id().unwrap();
903 let factory = EventFactory::new();
904
905 server.mock_room_state_encryption().plain().mount().await;
906
907 let space_child_event_id = event_id!("$1");
908 let space_parent_event_id = event_id!("$2");
909 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
910 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
911
912 let space_id = room_id!("!my_space:example.org");
913 let child_id = room_id!("!my_child:example.org");
914
915 add_space_rooms(
916 vec![
917 MockSpaceRoomParameters {
918 room_id: space_id,
919 order: None,
920 parents: vec![],
921 children: vec![],
922 power_level: Some(100),
923 },
924 MockSpaceRoomParameters {
925 room_id: child_id,
926 order: None,
927 parents: vec![],
928 children: vec![],
929 power_level: Some(100),
930 },
931 ],
932 &client,
933 &server,
934 &factory,
935 user_id,
936 )
937 .await;
938
939 let space_service = SpaceService::new(client.clone());
940
941 let result =
943 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
944
945 assert!(result.is_ok());
947 }
948
949 #[async_test]
950 async fn test_add_child_to_space_without_space_admin() {
951 let server = MatrixMockServer::new().await;
953 let client = server.client_builder().build().await;
954 let user_id = client.user_id().unwrap();
955 let factory = EventFactory::new();
956
957 server.mock_room_state_encryption().plain().mount().await;
958
959 server.mock_set_space_child().unauthorized().expect(1).mount().await;
960 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
961
962 let space_id = room_id!("!my_space:example.org");
963 let child_id = room_id!("!my_child:example.org");
964
965 add_space_rooms(
966 vec![
967 MockSpaceRoomParameters {
968 room_id: space_id,
969 order: None,
970 parents: vec![],
971 children: vec![],
972 power_level: Some(0),
973 },
974 MockSpaceRoomParameters {
975 room_id: child_id,
976 order: None,
977 parents: vec![],
978 children: vec![],
979 power_level: Some(0),
980 },
981 ],
982 &client,
983 &server,
984 &factory,
985 user_id,
986 )
987 .await;
988
989 let space_service = SpaceService::new(client.clone());
990
991 let result =
993 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
994
995 assert!(result.is_err());
998 }
999
1000 #[async_test]
1001 async fn test_add_child_to_space_without_child_admin() {
1002 let server = MatrixMockServer::new().await;
1005 let client = server.client_builder().build().await;
1006 let user_id = client.user_id().unwrap();
1007 let factory = EventFactory::new();
1008
1009 server.mock_room_state_encryption().plain().mount().await;
1010
1011 let space_child_event_id = event_id!("$1");
1012 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1013 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1014
1015 let space_id = room_id!("!my_space:example.org");
1016 let child_id = room_id!("!my_child:example.org");
1017
1018 add_space_rooms(
1019 vec![
1020 MockSpaceRoomParameters {
1021 room_id: space_id,
1022 order: None,
1023 parents: vec![],
1024 children: vec![],
1025 power_level: Some(100),
1026 },
1027 MockSpaceRoomParameters {
1028 room_id: child_id,
1029 order: None,
1030 parents: vec![],
1031 children: vec![],
1032 power_level: Some(0),
1033 },
1034 ],
1035 &client,
1036 &server,
1037 &factory,
1038 user_id,
1039 )
1040 .await;
1041
1042 let space_service = SpaceService::new(client.clone());
1043
1044 let result =
1046 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1047
1048 error!("result: {:?}", result);
1049 assert!(result.is_ok());
1052 }
1053
1054 #[async_test]
1055 async fn test_remove_child_from_space() {
1056 let server = MatrixMockServer::new().await;
1058 let client = server.client_builder().build().await;
1059 let user_id = client.user_id().unwrap();
1060 let factory = EventFactory::new();
1061
1062 server.mock_room_state_encryption().plain().mount().await;
1063
1064 let space_child_event_id = event_id!("$1");
1065 let space_parent_event_id = event_id!("$2");
1066 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1067 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1068
1069 let parent_id = room_id!("!parent_space:example.org");
1070 let child_id = room_id!("!child_space:example.org");
1071
1072 add_space_rooms(
1073 vec![
1074 MockSpaceRoomParameters {
1075 room_id: parent_id,
1076 order: None,
1077 parents: vec![],
1078 children: vec![child_id],
1079 power_level: None,
1080 },
1081 MockSpaceRoomParameters {
1082 room_id: child_id,
1083 order: None,
1084 parents: vec![parent_id],
1085 children: vec![],
1086 power_level: None,
1087 },
1088 ],
1089 &client,
1090 &server,
1091 &factory,
1092 user_id,
1093 )
1094 .await;
1095
1096 let space_service = SpaceService::new(client.clone());
1097
1098 let result =
1100 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1101
1102 assert!(result.is_ok());
1104 }
1105
1106 #[async_test]
1107 async fn test_remove_child_from_space_without_parent_event() {
1108 let server = MatrixMockServer::new().await;
1110 let client = server.client_builder().build().await;
1111 let user_id = client.user_id().unwrap();
1112 let factory = EventFactory::new();
1113
1114 server.mock_room_state_encryption().plain().mount().await;
1115
1116 let space_child_event_id = event_id!("$1");
1117 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1118 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1119
1120 let parent_id = room_id!("!parent_space:example.org");
1121 let child_id = room_id!("!child_space:example.org");
1122
1123 add_space_rooms(
1124 vec![
1125 MockSpaceRoomParameters {
1126 room_id: parent_id,
1127 order: None,
1128 parents: vec![],
1129 children: vec![child_id],
1130 power_level: None,
1131 },
1132 MockSpaceRoomParameters {
1133 room_id: child_id,
1134 order: None,
1135 parents: vec![],
1136 children: vec![],
1137 power_level: None,
1138 },
1139 ],
1140 &client,
1141 &server,
1142 &factory,
1143 user_id,
1144 )
1145 .await;
1146
1147 let space_service = SpaceService::new(client.clone());
1148
1149 let result =
1151 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1152
1153 assert!(result.is_ok());
1156 }
1157
1158 #[async_test]
1159 async fn test_remove_child_from_space_without_child_event() {
1160 let server = MatrixMockServer::new().await;
1162 let client = server.client_builder().build().await;
1163 let user_id = client.user_id().unwrap();
1164 let factory = EventFactory::new();
1165
1166 server.mock_room_state_encryption().plain().mount().await;
1167
1168 let space_parent_event_id = event_id!("$2");
1169 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1170 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1171
1172 let parent_id = room_id!("!parent_space:example.org");
1173 let child_id = room_id!("!child_space:example.org");
1174
1175 add_space_rooms(
1176 vec![
1177 MockSpaceRoomParameters {
1178 room_id: parent_id,
1179 order: None,
1180 parents: vec![],
1181 children: vec![],
1182 power_level: None,
1183 },
1184 MockSpaceRoomParameters {
1185 room_id: child_id,
1186 order: None,
1187 parents: vec![parent_id],
1188 children: vec![],
1189 power_level: None,
1190 },
1191 ],
1192 &client,
1193 &server,
1194 &factory,
1195 user_id,
1196 )
1197 .await;
1198
1199 let space_service = SpaceService::new(client.clone());
1200
1201 let result =
1203 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1204
1205 assert!(result.is_ok());
1208 }
1209
1210 async fn add_space_rooms(
1211 rooms: Vec<MockSpaceRoomParameters>,
1212 client: &Client,
1213 server: &MatrixMockServer,
1214 factory: &EventFactory,
1215 user_id: &UserId,
1216 ) {
1217 for parameters in rooms {
1218 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
1219 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
1220
1221 if let Some(order) = parameters.order {
1222 builder = builder.add_account_data(RoomAccountDataTestEvent::Custom(json!({
1223 "type": "m.space_order",
1224 "content": {
1225 "order": order
1226 }
1227 })));
1228 }
1229
1230 for parent_id in parameters.parents {
1231 builder = builder.add_state_event(
1232 factory
1233 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
1234 .sender(user_id),
1235 );
1236 }
1237
1238 for child_id in parameters.children {
1239 builder = builder.add_state_event(
1240 factory
1241 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
1242 .sender(user_id),
1243 );
1244 }
1245
1246 if let Some(power_level) = parameters.power_level {
1247 let mut power_levels = BTreeMap::from([(user_id.to_owned(), power_level.into())]);
1248
1249 builder = builder.add_state_event(
1250 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
1251 );
1252 }
1253
1254 server.sync_room(client, builder).await;
1255 }
1256 }
1257
1258 struct MockSpaceRoomParameters {
1259 room_id: &'static RoomId,
1260 order: Option<&'static str>,
1261 parents: Vec<&'static RoomId>,
1262 children: Vec<&'static RoomId>,
1263 power_level: Option<i32>,
1264 }
1265}