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