1use std::{
31 cmp::Ordering,
32 collections::{HashMap, HashSet},
33 sync::Arc,
34};
35
36use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
37use futures_util::{future::join_all, pin_mut};
38use imbl::Vector;
39use itertools::Itertools;
40use matrix_sdk::{
41 Client, Error as SDKError, Room, deserialized_responses::SyncOrStrippedState,
42 task_monitor::BackgroundTaskHandle,
43};
44use ruma::{
45 OwnedRoomId, RoomId, SpaceChildOrder,
46 events::{
47 self, StateEventType, SyncStateEvent,
48 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
49 },
50};
51use thiserror::Error;
52use tokio::sync::Mutex as AsyncMutex;
53use tracing::{error, trace, warn};
54
55use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle, room::SpaceRoomChildState};
56pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
57
58pub mod graph;
59pub mod leave;
60pub mod room;
61pub mod room_list;
62
63#[derive(Debug, Error)]
65pub enum Error {
66 #[error("User ID not available from client")]
68 UserIdNotFound,
69
70 #[error("Room `{0}` not found")]
72 RoomNotFound(OwnedRoomId),
73
74 #[error("Missing `{0}` for `{1}`")]
76 MissingState(StateEventType, OwnedRoomId),
77
78 #[error("Failed to set either of the m.space.parent or m.space.child state events")]
81 UpdateRelationship(SDKError),
82
83 #[error(
86 "Failed to set the expected m.space.parent state event (but any m.space.child changes were successful)"
87 )]
88 UpdateInverseRelationship(SDKError),
89
90 #[error("Failed to leave space")]
92 LeaveSpace(SDKError),
93
94 #[error("Failed to load members")]
96 LoadRoomMembers(SDKError),
97}
98
99struct SpaceState {
100 graph: SpaceGraph,
101 top_level_joined_spaces: ObservableVector<SpaceRoom>,
102 space_filters: ObservableVector<SpaceFilter>,
103}
104
105pub struct SpaceService {
145 client: Client,
146
147 space_state: Arc<AsyncMutex<SpaceState>>,
148
149 _room_update_handle: AsyncMutex<BackgroundTaskHandle>,
150}
151
152impl SpaceService {
153 pub async fn new(client: Client) -> Self {
155 let space_state = Arc::new(AsyncMutex::new(SpaceState {
156 graph: SpaceGraph::new(),
157 top_level_joined_spaces: ObservableVector::new(),
158 space_filters: ObservableVector::new(),
159 }));
160
161 let room_update_handle = client
162 .task_monitor()
163 .spawn_infinite_task("space_service", {
164 let client = client.clone();
165 let space_state = Arc::clone(&space_state);
166 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
167
168 async move {
169 pin_mut!(all_room_updates_receiver);
170
171 loop {
172 match all_room_updates_receiver.recv().await {
173 Ok(updates) => {
174 if updates.is_empty() {
175 continue;
176 }
177
178 let (spaces, filters, graph) =
179 Self::build_space_state(&client).await;
180 Self::update_space_state_if_needed(
181 Vector::from(spaces),
182 Vector::from(filters),
183 graph,
184 &space_state,
185 )
186 .await;
187 }
188 Err(err) => {
189 error!("error when listening to room updates: {err}");
190 }
191 }
192 }
193 }
194 })
195 .abort_on_drop();
196
197 let (spaces, filters, graph) = Self::build_space_state(&client).await;
199 Self::update_space_state_if_needed(
200 Vector::from(spaces),
201 Vector::from(filters),
202 graph,
203 &space_state,
204 )
205 .await;
206
207 Self { client, space_state, _room_update_handle: AsyncMutex::new(room_update_handle) }
208 }
209
210 pub async fn subscribe_to_top_level_joined_spaces(
213 &self,
214 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
215 self.space_state
216 .lock()
217 .await
218 .top_level_joined_spaces
219 .subscribe()
220 .into_values_and_batched_stream()
221 }
222
223 pub async fn top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
227 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
228
229 Self::update_space_state_if_needed(
230 Vector::from(top_level_joined_spaces.clone()),
231 Vector::from(filters),
232 graph,
233 &self.space_state,
234 )
235 .await;
236
237 top_level_joined_spaces
238 }
239
240 pub async fn space_filters(&self) -> Vec<SpaceFilter> {
285 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
286
287 Self::update_space_state_if_needed(
288 Vector::from(top_level_joined_spaces),
289 Vector::from(filters.clone()),
290 graph,
291 &self.space_state,
292 )
293 .await;
294
295 filters
296 }
297
298 pub async fn subscribe_to_space_filters(
300 &self,
301 ) -> (Vector<SpaceFilter>, VectorSubscriberBatchedStream<SpaceFilter>) {
302 self.space_state.lock().await.space_filters.subscribe().into_values_and_batched_stream()
303 }
304
305 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
311 let Some(user_id) = self.client.user_id() else {
312 return vec![];
313 };
314
315 let graph = &self.space_state.lock().await.graph;
316 let rooms = self.client.joined_space_rooms();
317
318 let mut editable_spaces = Vec::new();
319 for room in &rooms {
320 if let Ok(power_levels) = room.power_levels().await
321 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
322 {
323 let room_id = room.room_id();
324 editable_spaces.push(
325 SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64).await,
326 );
327 }
328 }
329
330 editable_spaces
331 }
332
333 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
335 SpaceRoomList::new(self.client.clone(), space_id).await
336 }
337
338 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
340 let graph = &self.space_state.lock().await.graph;
341
342 let rooms = graph
343 .parents_of(child_id)
344 .into_iter()
345 .filter_map(|parent_id| self.client.get_room(parent_id));
346
347 join_all(rooms.map(|room| async move {
348 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64).await
349 }))
350 .await
351 }
352
353 pub async fn get_space_room(&self, room_id: &RoomId) -> Option<SpaceRoom> {
356 let graph = &self.space_state.lock().await.graph;
357
358 if graph.has_node(room_id)
359 && let Some(room) = self.client.get_room(room_id)
360 {
361 Some(
362 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64)
363 .await,
364 )
365 } else {
366 None
367 }
368 }
369
370 pub async fn add_child_to_space(
371 &self,
372 child_id: OwnedRoomId,
373 space_id: OwnedRoomId,
374 ) -> Result<(), Error> {
375 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
376 let space_room =
377 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
378 let child_room =
379 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
380 let child_power_levels = child_room
381 .power_levels()
382 .await
383 .map_err(|error| Error::UpdateRelationship(matrix_sdk::Error::from(error)))?;
384
385 let child_route = child_room.route().await.map_err(Error::UpdateRelationship)?;
387 space_room
388 .send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
389 .await
390 .map_err(Error::UpdateRelationship)?;
391
392 if child_power_levels.user_can_send_state(user_id, StateEventType::SpaceParent) {
394 let parent_route =
395 space_room.route().await.map_err(Error::UpdateInverseRelationship)?;
396 child_room
397 .send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
398 .await
399 .map_err(Error::UpdateInverseRelationship)?;
400 } else {
401 warn!("The current user doesn't have permission to set the child's parent.");
402 }
403
404 Ok(())
405 }
406
407 pub async fn remove_child_from_space(
408 &self,
409 child_id: OwnedRoomId,
410 space_id: OwnedRoomId,
411 ) -> Result<(), Error> {
412 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
413 let space_room =
414 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
415
416 if let Ok(Some(_)) =
417 space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
418 {
419 space_room
426 .send_state_event_raw("m.space.child", child_id.as_str(), serde_json::json!({}))
427 .await
428 .map_err(Error::UpdateRelationship)?;
429 } else {
430 warn!("A space child event wasn't found on the parent, ignoring.");
431 }
432
433 if let Some(child_room) = self.client.get_room(&child_id) {
434 let power_levels = child_room.power_levels().await.map_err(|error| {
435 Error::UpdateInverseRelationship(matrix_sdk::Error::from(error))
436 })?;
437
438 if power_levels.user_can_send_state(user_id, StateEventType::SpaceParent)
439 && let Ok(Some(_)) = child_room
440 .get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id)
441 .await
442 {
443 child_room
445 .send_state_event_raw(
446 "m.space.parent",
447 space_id.as_str(),
448 serde_json::json!({}),
449 )
450 .await
451 .map_err(Error::UpdateInverseRelationship)?;
452 } else {
453 warn!("A space parent event wasn't found on the child, ignoring.");
454 }
455 } else {
456 warn!("The child room is unknown, skipping m.space.parent removal.");
457 }
458
459 Ok(())
460 }
461
462 pub async fn leave_space(&self, space_id: &RoomId) -> Result<LeaveSpaceHandle, Error> {
470 let space_state = self.space_state.lock().await;
471
472 if !space_state.graph.has_node(space_id) {
473 return Err(Error::RoomNotFound(space_id.to_owned()));
474 }
475
476 let room_ids = space_state.graph.flattened_bottom_up_subtree(space_id);
477
478 let handle = LeaveSpaceHandle::new(self.client.clone(), room_ids).await;
479
480 Ok(handle)
481 }
482
483 async fn update_space_state_if_needed(
484 new_spaces: Vector<SpaceRoom>,
485 new_filters: Vector<SpaceFilter>,
486 new_graph: SpaceGraph,
487 space_state: &Arc<AsyncMutex<SpaceState>>,
488 ) {
489 let mut space_state = space_state.lock().await;
490
491 if new_spaces != space_state.top_level_joined_spaces.clone() {
492 space_state.top_level_joined_spaces.clear();
493 space_state.top_level_joined_spaces.append(new_spaces);
494 }
495
496 if new_filters != space_state.space_filters.clone() {
497 space_state.space_filters.clear();
498 space_state.space_filters.append(new_filters);
499 }
500
501 space_state.graph = new_graph;
502 }
503
504 async fn build_space_state(client: &Client) -> (Vec<SpaceRoom>, Vec<SpaceFilter>, SpaceGraph) {
505 let joined_spaces = client.joined_space_rooms();
506 let joined_space_ids =
507 joined_spaces.iter().map(|space| space.room_id()).collect::<HashSet<_>>();
508
509 let mut graph = SpaceGraph::new();
511
512 let mut space_child_states = HashMap::<OwnedRoomId, SpaceRoomChildState>::new();
514
515 for space in joined_spaces.iter() {
518 graph.add_node(space.room_id().to_owned());
519
520 if let Ok(parents) = space.get_state_events_static::<SpaceParentEventContent>().await {
521 parents.into_iter()
522 .flat_map(|parent_event| match parent_event.deserialize() {
523 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
524 Some(e.state_key)
525 }
526 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
527 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
528 Err(e) => {
529 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.parent: {e}");
530 None
531 }
532 })
533 .filter(|parent| joined_space_ids.contains(&**parent))
534 .for_each(|parent| graph.add_edge(parent, space.room_id().to_owned()));
535 } else {
536 error!(room_id = ?space.room_id(), "Could not get m.space.parent events");
537 }
538
539 if let Ok(children) = space.get_state_events_static::<SpaceChildEventContent>().await {
540 children.into_iter()
541 .filter_map(|child_event| match child_event.deserialize() {
542 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
543 space_child_states.insert(
544 e.state_key.to_owned(),
545 SpaceRoomChildState {
546 order: e.content.order.clone(),
547 origin_server_ts: e.origin_server_ts,
548 },
549 );
550
551 Some(e.state_key)
552 }
553 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
554 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
555 Err(e) => {
556 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.child: {e}");
557 None
558 }
559 }).for_each(|child| graph.add_edge(space.room_id().to_owned(), child));
560 } else {
561 error!(room_id = ?space.room_id(), "Could not get m.space.child events");
562 }
563 }
564
565 graph.remove_cycles();
568
569 let root_nodes = graph.root_nodes();
570
571 let top_level_space_rooms = joined_spaces
575 .iter()
576 .filter(|room| root_nodes.contains(&room.room_id()))
577 .collect::<Vec<_>>();
578
579 let mut top_level_space_order = HashMap::new();
580 for space in &top_level_space_rooms {
581 if let Ok(Some(raw_event)) =
582 space.account_data_static::<events::space_order::SpaceOrderEventContent>().await
583 && let Ok(event) = raw_event.deserialize()
584 {
585 top_level_space_order.insert(space.room_id().to_owned(), event.content.order);
586 }
587 }
588
589 let top_level_space_rooms = top_level_space_rooms
590 .into_iter()
591 .sorted_by(|a, b| {
592 let a = (a.room_id(), top_level_space_order.get(a.room_id()).map(AsRef::as_ref));
593 let b = (b.room_id(), top_level_space_order.get(b.room_id()).map(AsRef::as_ref));
594
595 compare_top_level_space_rooms(a, b)
596 })
597 .collect::<Vec<_>>();
598
599 let mut top_level_spaces = Vec::new();
600
601 for room in &top_level_space_rooms {
602 top_level_spaces.push(
603 SpaceRoom::new_from_known(room, graph.children_of(room.room_id()).len() as u64)
604 .await,
605 );
606 }
607
608 let space_filters =
609 Self::build_space_filters(client, &graph, top_level_space_rooms, space_child_states)
610 .await;
611
612 (top_level_spaces, space_filters, graph)
613 }
614
615 async fn build_space_filters(
624 client: &Client,
625 graph: &SpaceGraph,
626 top_level_space_rooms: Vec<&Room>,
627 space_child_states: HashMap<OwnedRoomId, SpaceRoomChildState>,
628 ) -> Vec<SpaceFilter> {
629 let mut filters = Vec::new();
630 for top_level_space in top_level_space_rooms {
631 let children = graph
632 .children_of(top_level_space.room_id())
633 .into_iter()
634 .map(|id| id.to_owned())
635 .collect::<Vec<_>>();
636
637 filters.push(SpaceFilter {
638 space_room: SpaceRoom::new_from_known(top_level_space, children.len() as u64).await,
639 level: 0,
640 descendants: children.clone(),
641 });
642
643 let children_rooms = join_all(
644 children
645 .iter()
646 .filter_map(|child| client.get_room(child))
647 .filter(|room| room.is_space())
648 .map(|room| async move {
649 SpaceRoom::new_from_known(
650 &room,
651 graph.children_of(room.room_id()).len() as u64,
652 )
653 .await
654 }),
655 )
656 .await;
657 filters.append(
658 &mut children_rooms
659 .into_iter()
660 .sorted_by(|a, b| {
661 let a_state = space_child_states.get(&a.room_id).cloned();
662 let b_state = space_child_states.get(&b.room_id).cloned();
663
664 SpaceRoom::compare_rooms(
665 (&a.room_id, a_state.as_ref()),
666 (&b.room_id, b_state.as_ref()),
667 )
668 })
669 .map(|space_room| {
670 let descendants = graph.flattened_bottom_up_subtree(&space_room.room_id);
671
672 SpaceFilter { space_room, level: 1, descendants }
673 })
674 .collect::<Vec<_>>(),
675 );
676 }
677
678 filters
679 }
680}
681
682fn compare_top_level_space_rooms(
684 a: (&RoomId, Option<&SpaceChildOrder>),
685 b: (&RoomId, Option<&SpaceChildOrder>),
686) -> Ordering {
687 let (a_room_id, a_order) = a;
688 let (b_room_id, b_order) = b;
689
690 match (a_order, b_order) {
691 (Some(a_order), Some(b_order)) => a_order.cmp(b_order).then(a_room_id.cmp(b_room_id)),
692 (Some(_), None) => Ordering::Less,
693 (None, Some(_)) => Ordering::Greater,
694 (None, None) => a_room_id.cmp(b_room_id),
695 }
696}
697
698#[derive(Debug, Clone, PartialEq)]
699pub struct SpaceFilter {
700 pub space_room: SpaceRoom,
702
703 pub level: u8,
706
707 pub descendants: Vec<OwnedRoomId>,
711}
712
713#[cfg(test)]
714mod tests {
715 use std::collections::BTreeMap;
716
717 use assert_matches2::assert_let;
718 use eyeball_im::VectorDiff;
719 use futures_util::{StreamExt, pin_mut};
720 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
721 use matrix_sdk_test::{
722 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
723 };
724 use proptest::prelude::*;
725 use ruma::{
726 MilliSecondsSinceUnixEpoch, OwnedSpaceChildOrder, RoomVersionId, UserId, event_id,
727 owned_room_id, room_id, serde::Raw,
728 };
729 use serde_json::json;
730 use stream_assert::{assert_next_eq, assert_pending};
731
732 use super::*;
733
734 #[async_test]
735 async fn test_spaces_hierarchy() {
736 let server = MatrixMockServer::new().await;
737 let client = server.client_builder().build().await;
738 let user_id = client.user_id().unwrap();
739 let space_service = SpaceService::new(client.clone()).await;
740 let factory = EventFactory::new();
741
742 server.mock_room_state_encryption().plain().mount().await;
743
744 let parent_space_id = room_id!("!parent_space:example.org");
747 let child_space_id_1 = room_id!("!child_space_1:example.org");
748 let child_space_id_2 = room_id!("!child_space_2:example.org");
749
750 add_space_rooms(
751 vec![
752 MockSpaceRoomParameters {
753 room_id: child_space_id_1,
754 order: None,
755 parents: vec![parent_space_id],
756 children: vec![],
757 power_level: None,
758 },
759 MockSpaceRoomParameters {
760 room_id: child_space_id_2,
761 order: None,
762 parents: vec![parent_space_id],
763 children: vec![],
764 power_level: None,
765 },
766 MockSpaceRoomParameters {
767 room_id: parent_space_id,
768 order: None,
769 parents: vec![],
770 children: vec![child_space_id_1, child_space_id_2],
771 power_level: None,
772 },
773 ],
774 &client,
775 &server,
776 &factory,
777 user_id,
778 )
779 .await;
780
781 assert_eq!(
783 space_service
784 .top_level_joined_spaces()
785 .await
786 .iter()
787 .map(|s| s.room_id.to_owned())
788 .collect::<Vec<_>>(),
789 vec![parent_space_id]
790 );
791
792 assert_eq!(
794 space_service
795 .top_level_joined_spaces()
796 .await
797 .iter()
798 .map(|s| s.children_count)
799 .collect::<Vec<_>>(),
800 vec![2]
801 );
802
803 let parent_space = client.get_room(parent_space_id).unwrap();
804 assert!(parent_space.is_space());
805
806 let spaces: Vec<ParentSpace> = client
809 .get_room(child_space_id_1)
810 .unwrap()
811 .parent_spaces()
812 .await
813 .unwrap()
814 .map(Result::unwrap)
815 .collect()
816 .await;
817
818 assert_let!(ParentSpace::Reciprocal(parent) = spaces.first().unwrap());
819 assert_eq!(parent.room_id(), parent_space.room_id());
820
821 let spaces: Vec<ParentSpace> = client
822 .get_room(child_space_id_2)
823 .unwrap()
824 .parent_spaces()
825 .await
826 .unwrap()
827 .map(Result::unwrap)
828 .collect()
829 .await;
830
831 assert_let!(ParentSpace::Reciprocal(parent) = spaces.last().unwrap());
832 assert_eq!(parent.room_id(), parent_space.room_id());
833 }
834
835 #[async_test]
836 async fn test_joined_spaces_updates() {
837 let server = MatrixMockServer::new().await;
838 let client = server.client_builder().build().await;
839 let user_id = client.user_id().unwrap();
840 let factory = EventFactory::new();
841
842 server.mock_room_state_encryption().plain().mount().await;
843
844 let first_space_id = room_id!("!first_space:example.org");
845 let second_space_id = room_id!("!second_space:example.org");
846
847 server
849 .sync_room(
850 &client,
851 JoinedRoomBuilder::new(first_space_id)
852 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type()),
853 )
854 .await;
855
856 let space_service = SpaceService::new(client.clone()).await;
860
861 let (initial_values, joined_spaces_subscriber) =
862 space_service.subscribe_to_top_level_joined_spaces().await;
863 pin_mut!(joined_spaces_subscriber);
864 assert_pending!(joined_spaces_subscriber);
865
866 assert_eq!(
867 initial_values,
868 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
869 .into()
870 );
871
872 assert_eq!(
873 space_service.top_level_joined_spaces().await,
874 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
875 );
876
877 assert_pending!(joined_spaces_subscriber);
880
881 server
884 .sync_room(
885 &client,
886 JoinedRoomBuilder::new(second_space_id)
887 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
888 .add_state_event(
889 factory
890 .space_child(
891 second_space_id.to_owned(),
892 owned_room_id!("!child:example.org"),
893 )
894 .sender(user_id),
895 ),
896 )
897 .await;
898
899 assert_eq!(
901 space_service.top_level_joined_spaces().await,
902 vec![
903 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await,
904 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1).await
905 ]
906 );
907
908 assert_next_eq!(
909 joined_spaces_subscriber,
910 vec![
911 VectorDiff::Clear,
912 VectorDiff::Append {
913 values: vec![
914 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)
915 .await,
916 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
917 .await
918 ]
919 .into()
920 },
921 ]
922 );
923
924 server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
925
926 assert_next_eq!(
928 joined_spaces_subscriber,
929 vec![
930 VectorDiff::Clear,
931 VectorDiff::Append {
932 values: vec![
933 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)
934 .await
935 ]
936 .into()
937 },
938 ]
939 );
940
941 server
943 .sync_room(
944 &client,
945 JoinedRoomBuilder::new(room_id!("!room:example.org"))
946 .add_state_event(factory.create(user_id, RoomVersionId::V1)),
947 )
948 .await;
949
950 assert_pending!(joined_spaces_subscriber);
952 assert_eq!(
953 space_service.top_level_joined_spaces().await,
954 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0).await]
955 );
956 }
957
958 #[async_test]
959 async fn test_joined_child_space_becomes_top_level_after_leaving_parent() {
960 let server = MatrixMockServer::new().await;
961 let client = server.client_builder().build().await;
962
963 server.mock_room_state_encryption().plain().mount().await;
964
965 let parent_space_id = room_id!("!parent_space:example.org");
966 let child_space_id = room_id!("!child_space:example.org");
967 let child_room_id = room_id!("!child_room:example.org");
968
969 add_space_rooms(
970 vec![
971 MockSpaceRoomParameters {
972 room_id: child_space_id,
973 order: None,
974 parents: vec![parent_space_id],
975 children: vec![child_room_id],
976 power_level: None,
977 },
978 MockSpaceRoomParameters {
979 room_id: parent_space_id,
980 order: None,
981 parents: vec![],
982 children: vec![child_space_id],
983 power_level: None,
984 },
985 ],
986 &client,
987 &server,
988 &EventFactory::new(),
989 client.user_id().unwrap(),
990 )
991 .await;
992
993 let space_service = SpaceService::new(client.clone()).await;
994
995 let top_level_spaces = space_service.top_level_joined_spaces().await;
996 assert_eq!(top_level_spaces.len(), 1);
997 assert_eq!(top_level_spaces[0].room_id, parent_space_id);
998
999 server.sync_room(&client, LeftRoomBuilder::new(parent_space_id)).await;
1000
1001 let top_level_spaces = space_service.top_level_joined_spaces().await;
1002 assert_eq!(top_level_spaces.len(), 1);
1003 assert_eq!(top_level_spaces[0].room_id, child_space_id);
1004 assert_eq!(top_level_spaces[0].children_count, 1);
1005
1006 let filters = space_service.space_filters().await;
1007 assert_eq!(filters.len(), 1);
1008 assert_eq!(filters[0].space_room.room_id, child_space_id);
1009 assert_eq!(filters[0].descendants, vec![child_room_id]);
1010 }
1011
1012 #[async_test]
1013 async fn test_space_filters() {
1014 let server = MatrixMockServer::new().await;
1015 let client = server.client_builder().build().await;
1016
1017 server.mock_room_state_encryption().plain().mount().await;
1018
1019 add_space_rooms(
1020 vec![
1021 MockSpaceRoomParameters {
1022 room_id: room_id!("!1:a.b"),
1023 order: None,
1024 parents: vec![],
1025 children: vec![],
1026 power_level: None,
1027 },
1028 MockSpaceRoomParameters {
1029 room_id: room_id!("!1.2:a.b"),
1030 order: None,
1031 parents: vec![room_id!("!1:a.b")],
1032 children: vec![],
1033 power_level: None,
1034 },
1035 MockSpaceRoomParameters {
1036 room_id: room_id!("!1.2.3:a.b"),
1037 order: None,
1038 parents: vec![room_id!("!1.2:a.b")],
1039 children: vec![],
1040 power_level: None,
1041 },
1042 MockSpaceRoomParameters {
1043 room_id: room_id!("!1.2.3.4:a.b"),
1044 order: None,
1045 parents: vec![room_id!("!1.2.3:a.b")],
1046 children: vec![],
1047 power_level: None,
1048 },
1049 ],
1050 &client,
1051 &server,
1052 &EventFactory::new(),
1053 client.user_id().unwrap(),
1054 )
1055 .await;
1056
1057 let space_service = SpaceService::new(client.clone()).await;
1058
1059 let filters = space_service.space_filters().await;
1060 assert_eq!(filters.len(), 2);
1061 assert_eq!(filters[0].space_room.room_id, room_id!("!1:a.b"));
1062 assert_eq!(filters[0].level, 0);
1063 assert_eq!(filters[0].descendants.len(), 1); assert_eq!(filters[1].space_room.room_id, room_id!("!1.2:a.b"));
1065 assert_eq!(filters[1].level, 1);
1066 assert_eq!(filters[1].descendants.len(), 3);
1067
1068 let (initial_values, space_filters_subscriber) =
1069 space_service.subscribe_to_space_filters().await;
1070 pin_mut!(space_filters_subscriber);
1071 assert_pending!(space_filters_subscriber);
1072
1073 assert_eq!(initial_values, filters.into());
1074
1075 add_space_rooms(
1076 vec![MockSpaceRoomParameters {
1077 room_id: room_id!("!1.2.3.4.5:a.b"),
1078 order: None,
1079 parents: vec![room_id!("!1.2.3.4:a.b")],
1080 children: vec![],
1081 power_level: None,
1082 }],
1083 &client,
1084 &server,
1085 &EventFactory::new(),
1086 client.user_id().unwrap(),
1087 )
1088 .await;
1089
1090 space_filters_subscriber.next().await;
1091
1092 let filters = space_service.space_filters().await;
1093 assert_eq!(filters[0].descendants.len(), 1);
1094 assert_eq!(filters[1].descendants.len(), 4);
1095 }
1096
1097 #[async_test]
1098 async fn test_top_level_space_order() {
1099 let server = MatrixMockServer::new().await;
1100 let client = server.client_builder().build().await;
1101
1102 server.mock_room_state_encryption().plain().mount().await;
1103
1104 add_space_rooms(
1105 vec![
1106 MockSpaceRoomParameters {
1107 room_id: room_id!("!2:a.b"),
1108 order: Some("2"),
1109 parents: vec![],
1110 children: vec![],
1111 power_level: None,
1112 },
1113 MockSpaceRoomParameters {
1114 room_id: room_id!("!4:a.b"),
1115 order: None,
1116 parents: vec![],
1117 children: vec![],
1118 power_level: None,
1119 },
1120 MockSpaceRoomParameters {
1121 room_id: room_id!("!3:a.b"),
1122 order: None,
1123 parents: vec![],
1124 children: vec![],
1125 power_level: None,
1126 },
1127 MockSpaceRoomParameters {
1128 room_id: room_id!("!1:a.b"),
1129 order: Some("1"),
1130 parents: vec![],
1131 children: vec![],
1132 power_level: None,
1133 },
1134 ],
1135 &client,
1136 &server,
1137 &EventFactory::new(),
1138 client.user_id().unwrap(),
1139 )
1140 .await;
1141
1142 let space_service = SpaceService::new(client.clone()).await;
1143
1144 assert_eq!(
1147 space_service.top_level_joined_spaces().await,
1148 vec![
1149 SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0).await,
1150 SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0).await,
1151 SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0).await,
1152 SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0).await,
1153 ]
1154 );
1155 }
1156
1157 #[async_test]
1158 async fn test_editable_spaces() {
1159 let server = MatrixMockServer::new().await;
1161 let client = server.client_builder().build().await;
1162 let user_id = client.user_id().unwrap();
1163 let factory = EventFactory::new();
1164
1165 server.mock_room_state_encryption().plain().mount().await;
1166
1167 let admin_space_id = room_id!("!admin_space:example.org");
1168 let admin_subspace_id = room_id!("!admin_subspace:example.org");
1169 let regular_space_id = room_id!("!regular_space:example.org");
1170 let regular_subspace_id = room_id!("!regular_subspace:example.org");
1171
1172 add_space_rooms(
1173 vec![
1174 MockSpaceRoomParameters {
1175 room_id: admin_space_id,
1176 order: None,
1177 parents: vec![],
1178 children: vec![regular_subspace_id],
1179 power_level: Some(100),
1180 },
1181 MockSpaceRoomParameters {
1182 room_id: admin_subspace_id,
1183 order: None,
1184 parents: vec![regular_space_id],
1185 children: vec![],
1186 power_level: Some(100),
1187 },
1188 MockSpaceRoomParameters {
1189 room_id: regular_space_id,
1190 order: None,
1191 parents: vec![],
1192 children: vec![admin_subspace_id],
1193 power_level: Some(0),
1194 },
1195 MockSpaceRoomParameters {
1196 room_id: regular_subspace_id,
1197 order: None,
1198 parents: vec![admin_space_id],
1199 children: vec![],
1200 power_level: Some(0),
1201 },
1202 ],
1203 &client,
1204 &server,
1205 &factory,
1206 user_id,
1207 )
1208 .await;
1209
1210 let space_service = SpaceService::new(client.clone()).await;
1211
1212 let editable_spaces = space_service.editable_spaces().await;
1214
1215 assert_eq!(
1217 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
1218 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
1219 );
1220 }
1221
1222 #[async_test]
1223 async fn test_joined_parents_of_child() {
1224 let server = MatrixMockServer::new().await;
1226 let client = server.client_builder().build().await;
1227 let user_id = client.user_id().unwrap();
1228 let factory = EventFactory::new();
1229
1230 server.mock_room_state_encryption().plain().mount().await;
1231
1232 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1233 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1234 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1235 let child_space_id = room_id!("!child_space:example.org");
1236
1237 add_space_rooms(
1238 vec![
1239 MockSpaceRoomParameters {
1240 room_id: child_space_id,
1241 order: None,
1242 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1243 children: vec![],
1244 power_level: None,
1245 },
1246 MockSpaceRoomParameters {
1247 room_id: parent_space_id_1,
1248 order: None,
1249 parents: vec![],
1250 children: vec![child_space_id],
1251 power_level: None,
1252 },
1253 MockSpaceRoomParameters {
1254 room_id: parent_space_id_2,
1255 order: None,
1256 parents: vec![],
1257 children: vec![child_space_id],
1258 power_level: None,
1259 },
1260 ],
1261 &client,
1262 &server,
1263 &factory,
1264 user_id,
1265 )
1266 .await;
1267
1268 let space_service = SpaceService::new(client.clone()).await;
1269
1270 let parents = space_service.joined_parents_of_child(child_space_id).await;
1272
1273 assert_eq!(
1275 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
1276 vec![parent_space_id_1, parent_space_id_2]
1277 );
1278 }
1279
1280 #[async_test]
1281 async fn test_get_space_room_for_id() {
1282 let server = MatrixMockServer::new().await;
1283 let client = server.client_builder().build().await;
1284 let user_id = client.user_id().unwrap();
1285 let factory = EventFactory::new();
1286
1287 server.mock_room_state_encryption().plain().mount().await;
1288
1289 let space_id = room_id!("!single_space:example.org");
1290
1291 add_space_rooms(
1292 vec![MockSpaceRoomParameters {
1293 room_id: space_id,
1294 order: None,
1295 parents: vec![],
1296 children: vec![],
1297 power_level: None,
1298 }],
1299 &client,
1300 &server,
1301 &factory,
1302 user_id,
1303 )
1304 .await;
1305
1306 let space_service = SpaceService::new(client.clone()).await;
1307
1308 let found = space_service.get_space_room(space_id).await;
1309 assert!(found.is_some());
1310
1311 let expected = SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await;
1312 assert_eq!(found.unwrap(), expected);
1313 }
1314
1315 #[async_test]
1316 async fn test_add_child_to_space() {
1317 let server = MatrixMockServer::new().await;
1319 let client = server.client_builder().build().await;
1320 let user_id = client.user_id().unwrap();
1321 let factory = EventFactory::new();
1322
1323 server.mock_room_state_encryption().plain().mount().await;
1324
1325 let space_child_event_id = event_id!("$1");
1326 let space_parent_event_id = event_id!("$2");
1327 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1328 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1329
1330 let space_id = room_id!("!my_space:example.org");
1331 let child_id = room_id!("!my_child:example.org");
1332
1333 add_space_rooms(
1334 vec![
1335 MockSpaceRoomParameters {
1336 room_id: space_id,
1337 order: None,
1338 parents: vec![],
1339 children: vec![],
1340 power_level: Some(100),
1341 },
1342 MockSpaceRoomParameters {
1343 room_id: child_id,
1344 order: None,
1345 parents: vec![],
1346 children: vec![],
1347 power_level: Some(100),
1348 },
1349 ],
1350 &client,
1351 &server,
1352 &factory,
1353 user_id,
1354 )
1355 .await;
1356
1357 let space_service = SpaceService::new(client.clone()).await;
1358
1359 let result =
1361 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1362
1363 assert!(result.is_ok());
1365 }
1366
1367 #[async_test]
1368 async fn test_add_child_to_space_without_space_admin() {
1369 let server = MatrixMockServer::new().await;
1371 let client = server.client_builder().build().await;
1372 let user_id = client.user_id().unwrap();
1373 let factory = EventFactory::new();
1374
1375 server.mock_room_state_encryption().plain().mount().await;
1376
1377 server.mock_set_space_child().unauthorized().expect(1).mount().await;
1378 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1379
1380 let space_id = room_id!("!my_space:example.org");
1381 let child_id = room_id!("!my_child:example.org");
1382
1383 add_space_rooms(
1384 vec![
1385 MockSpaceRoomParameters {
1386 room_id: space_id,
1387 order: None,
1388 parents: vec![],
1389 children: vec![],
1390 power_level: Some(0),
1391 },
1392 MockSpaceRoomParameters {
1393 room_id: child_id,
1394 order: None,
1395 parents: vec![],
1396 children: vec![],
1397 power_level: Some(0),
1398 },
1399 ],
1400 &client,
1401 &server,
1402 &factory,
1403 user_id,
1404 )
1405 .await;
1406
1407 let space_service = SpaceService::new(client.clone()).await;
1408
1409 let result =
1411 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1412
1413 assert!(result.is_err());
1416 }
1417
1418 #[async_test]
1419 async fn test_add_child_to_space_without_child_admin() {
1420 let server = MatrixMockServer::new().await;
1423 let client = server.client_builder().build().await;
1424 let user_id = client.user_id().unwrap();
1425 let factory = EventFactory::new();
1426
1427 server.mock_room_state_encryption().plain().mount().await;
1428
1429 let space_child_event_id = event_id!("$1");
1430 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1431 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1432
1433 let space_id = room_id!("!my_space:example.org");
1434 let child_id = room_id!("!my_child:example.org");
1435
1436 add_space_rooms(
1437 vec![
1438 MockSpaceRoomParameters {
1439 room_id: space_id,
1440 order: None,
1441 parents: vec![],
1442 children: vec![],
1443 power_level: Some(100),
1444 },
1445 MockSpaceRoomParameters {
1446 room_id: child_id,
1447 order: None,
1448 parents: vec![],
1449 children: vec![],
1450 power_level: Some(0),
1451 },
1452 ],
1453 &client,
1454 &server,
1455 &factory,
1456 user_id,
1457 )
1458 .await;
1459
1460 let space_service = SpaceService::new(client.clone()).await;
1461
1462 let result =
1464 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1465
1466 error!("result: {:?}", result);
1467 assert!(result.is_ok());
1470 }
1471
1472 #[async_test]
1473 async fn test_remove_child_from_space() {
1474 let server = MatrixMockServer::new().await;
1476 let client = server.client_builder().build().await;
1477 let user_id = client.user_id().unwrap();
1478 let factory = EventFactory::new();
1479
1480 server.mock_room_state_encryption().plain().mount().await;
1481
1482 let space_child_event_id = event_id!("$1");
1483 let space_parent_event_id = event_id!("$2");
1484 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1485 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1486
1487 let parent_id = room_id!("!parent_space:example.org");
1488 let child_id = room_id!("!child_space:example.org");
1489
1490 add_space_rooms(
1491 vec![
1492 MockSpaceRoomParameters {
1493 room_id: parent_id,
1494 order: None,
1495 parents: vec![],
1496 children: vec![child_id],
1497 power_level: None,
1498 },
1499 MockSpaceRoomParameters {
1500 room_id: child_id,
1501 order: None,
1502 parents: vec![parent_id],
1503 children: vec![],
1504 power_level: None,
1505 },
1506 ],
1507 &client,
1508 &server,
1509 &factory,
1510 user_id,
1511 )
1512 .await;
1513
1514 let space_service = SpaceService::new(client.clone()).await;
1515
1516 let result =
1518 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1519
1520 assert!(result.is_ok());
1522 }
1523
1524 #[async_test]
1525 async fn test_remove_child_from_space_without_parent_event() {
1526 let server = MatrixMockServer::new().await;
1528 let client = server.client_builder().build().await;
1529 let user_id = client.user_id().unwrap();
1530 let factory = EventFactory::new();
1531
1532 server.mock_room_state_encryption().plain().mount().await;
1533
1534 let space_child_event_id = event_id!("$1");
1535 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1536 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1537
1538 let parent_id = room_id!("!parent_space:example.org");
1539 let child_id = room_id!("!child_space:example.org");
1540
1541 add_space_rooms(
1542 vec![
1543 MockSpaceRoomParameters {
1544 room_id: parent_id,
1545 order: None,
1546 parents: vec![],
1547 children: vec![child_id],
1548 power_level: None,
1549 },
1550 MockSpaceRoomParameters {
1551 room_id: child_id,
1552 order: None,
1553 parents: vec![],
1554 children: vec![],
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 result =
1569 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1570
1571 assert!(result.is_ok());
1574 }
1575
1576 #[async_test]
1577 async fn test_remove_child_from_space_without_child_event() {
1578 let server = MatrixMockServer::new().await;
1580 let client = server.client_builder().build().await;
1581 let user_id = client.user_id().unwrap();
1582 let factory = EventFactory::new();
1583
1584 server.mock_room_state_encryption().plain().mount().await;
1585
1586 let space_parent_event_id = event_id!("$2");
1587 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1588 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1589
1590 let parent_id = room_id!("!parent_space:example.org");
1591 let child_id = room_id!("!child_space:example.org");
1592
1593 add_space_rooms(
1594 vec![
1595 MockSpaceRoomParameters {
1596 room_id: parent_id,
1597 order: None,
1598 parents: vec![],
1599 children: vec![],
1600 power_level: None,
1601 },
1602 MockSpaceRoomParameters {
1603 room_id: child_id,
1604 order: None,
1605 parents: vec![parent_id],
1606 children: vec![],
1607 power_level: None,
1608 },
1609 ],
1610 &client,
1611 &server,
1612 &factory,
1613 user_id,
1614 )
1615 .await;
1616
1617 let space_service = SpaceService::new(client.clone()).await;
1618
1619 let result =
1621 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1622
1623 assert!(result.is_ok());
1626 }
1627
1628 #[async_test]
1629 async fn test_remove_unknown_child_from_space() {
1630 let server = MatrixMockServer::new().await;
1632 let client = server.client_builder().build().await;
1633 let user_id = client.user_id().unwrap();
1634 let factory = EventFactory::new();
1635
1636 server.mock_room_state_encryption().plain().mount().await;
1637
1638 let space_child_event_id = event_id!("$1");
1639 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1640 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1642
1643 let parent_id = room_id!("!parent_space:example.org");
1644 let unknown_child_id = room_id!("!unknown_child:example.org");
1645
1646 add_space_rooms(
1648 vec![MockSpaceRoomParameters {
1649 room_id: parent_id,
1650 order: None,
1651 parents: vec![],
1652 children: vec![unknown_child_id],
1653 power_level: None,
1654 }],
1655 &client,
1656 &server,
1657 &factory,
1658 user_id,
1659 )
1660 .await;
1661
1662 assert!(client.get_room(unknown_child_id).is_none());
1664
1665 let space_service = SpaceService::new(client.clone()).await;
1666
1667 let result = space_service
1669 .remove_child_from_space(unknown_child_id.to_owned(), parent_id.to_owned())
1670 .await;
1671
1672 assert!(result.is_ok());
1675 }
1676
1677 #[async_test]
1678 async fn test_space_child_updates() {
1679 let server = MatrixMockServer::new().await;
1681 let client = server.client_builder().build().await;
1682 let user_id = client.user_id().unwrap();
1683 let factory = EventFactory::new();
1684
1685 server.mock_room_state_encryption().plain().mount().await;
1686
1687 let space_id = room_id!("!space:localhost");
1688 let first_child_id = room_id!("!first_child:localhost");
1689 let second_child_id = room_id!("!second_child:localhost");
1690
1691 server
1693 .sync_room(
1694 &client,
1695 JoinedRoomBuilder::new(space_id)
1696 .add_state_event(factory.create(user_id, RoomVersionId::V11).with_space_type()),
1697 )
1698 .await;
1699
1700 let space_service = SpaceService::new(client.clone()).await;
1703
1704 let (initial_values, joined_spaces_subscriber) =
1705 space_service.subscribe_to_top_level_joined_spaces().await;
1706 pin_mut!(joined_spaces_subscriber);
1707 assert_pending!(joined_spaces_subscriber);
1708
1709 assert_eq!(
1710 initial_values,
1711 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await].into()
1712 );
1713
1714 assert_eq!(
1715 space_service.top_level_joined_spaces().await,
1716 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0).await]
1717 );
1718
1719 server
1721 .sync_room(
1722 &client,
1723 JoinedRoomBuilder::new(space_id)
1724 .add_state_event(
1725 factory
1726 .space_child(space_id.to_owned(), first_child_id.to_owned())
1727 .sender(user_id),
1728 )
1729 .add_state_event(
1730 factory
1731 .space_child(space_id.to_owned(), second_child_id.to_owned())
1732 .sender(user_id),
1733 ),
1734 )
1735 .await;
1736
1737 assert_eq!(
1739 space_service.top_level_joined_spaces().await,
1740 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await]
1741 );
1742 assert_next_eq!(
1743 joined_spaces_subscriber,
1744 vec![
1745 VectorDiff::Clear,
1746 VectorDiff::Append {
1747 values: vec![
1748 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2).await
1749 ]
1750 .into()
1751 },
1752 ]
1753 );
1754
1755 server
1757 .sync_room(
1758 &client,
1759 JoinedRoomBuilder::new(space_id).add_state_bulk([Raw::new(&json!({
1760 "content": {},
1761 "type": "m.space.child",
1762 "event_id": "$cancelsecondchild",
1763 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
1764 "sender": user_id,
1765 "state_key": second_child_id,
1766 }))
1767 .unwrap()
1768 .cast_unchecked()]),
1769 )
1770 .await;
1771
1772 assert_eq!(
1774 space_service.top_level_joined_spaces().await,
1775 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await]
1776 );
1777 assert_next_eq!(
1778 joined_spaces_subscriber,
1779 vec![
1780 VectorDiff::Clear,
1781 VectorDiff::Append {
1782 values: vec![
1783 SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1).await
1784 ]
1785 .into()
1786 },
1787 ]
1788 );
1789 }
1790
1791 async fn add_space_rooms(
1792 rooms: Vec<MockSpaceRoomParameters>,
1793 client: &Client,
1794 server: &MatrixMockServer,
1795 factory: &EventFactory,
1796 user_id: &UserId,
1797 ) {
1798 for parameters in rooms {
1799 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
1800 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
1801
1802 if let Some(order) = parameters.order {
1803 builder = builder.add_account_data(factory.space_order(order));
1804 }
1805
1806 for parent_id in parameters.parents {
1807 builder = builder.add_state_event(
1808 factory
1809 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
1810 .sender(user_id),
1811 );
1812 }
1813
1814 for child_id in parameters.children {
1815 builder = builder.add_state_event(
1816 factory
1817 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
1818 .sender(user_id),
1819 );
1820 }
1821
1822 let mut power_levels = if let Some(power_level) = parameters.power_level {
1823 BTreeMap::from([(user_id.to_owned(), power_level.into())])
1824 } else {
1825 BTreeMap::from([(user_id.to_owned(), 100.into())])
1826 };
1827
1828 builder = builder.add_state_event(
1829 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
1830 );
1831
1832 server.sync_room(client, builder).await;
1833 }
1834 }
1835
1836 struct MockSpaceRoomParameters {
1837 room_id: &'static RoomId,
1838 order: Option<&'static str>,
1839 parents: Vec<&'static RoomId>,
1840 children: Vec<&'static RoomId>,
1841 power_level: Option<i32>,
1842 }
1843
1844 fn any_room_id_and_space_room_order()
1845 -> impl Strategy<Value = (OwnedRoomId, Option<OwnedSpaceChildOrder>)> {
1846 let room_id = "[a-zA-Z]{1,5}".prop_map(|r| {
1847 RoomId::new_v2(&r).expect("Any string starting with ! should be a valid room ID")
1848 });
1849
1850 let order = prop::option::of("[a-zA-Z]{1,5}").prop_map(|order| {
1851 order.map(|o| SpaceChildOrder::parse(o).expect("Any string should be a valid order"))
1852 });
1853
1854 (room_id, order)
1855 }
1856
1857 proptest! {
1858 #[test]
1859 fn sort_top_level_space_room_never_panics(mut v in prop::collection::vec(any_room_id_and_space_room_order(), 0..100)) {
1860 v.sort_by(|a, b| {
1861 let (a_room_id, a_order) = a;
1862 let (b_room_id, b_order) = b;
1863
1864 let a = (a_room_id.as_ref(), a_order.as_deref());
1865 let b = (b_room_id.as_ref(), b_order.as_deref());
1866
1867 compare_top_level_space_rooms(a, b)
1868 })
1869 }
1870
1871 #[test]
1872 fn test_compare_top_level_rooms_reflexive(a in any_room_id_and_space_room_order()) {
1873 let (a_room_id, a_order) = a;
1874 let a = (a_room_id.as_ref(), a_order.as_deref());
1875
1876 prop_assert_eq!(compare_top_level_space_rooms(a, a), Ordering::Equal);
1877 }
1878
1879 #[test]
1880 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()) {
1881 let (a_room_id, a_order) = a;
1882 let (b_room_id, b_order) = b;
1883
1884 let a = (a_room_id.as_ref(), a_order.as_deref());
1885 let b = (b_room_id.as_ref(), b_order.as_deref());
1886
1887 let ab = compare_top_level_space_rooms(a, b);
1888 let ba = compare_top_level_space_rooms(b, a);
1889
1890 prop_assert_eq!(ab, ba.reverse());
1891 }
1892
1893 #[test]
1894 fn test_compare_top_level_rooms_transitive(
1895 a in any_room_id_and_space_room_order(),
1896 b in any_room_id_and_space_room_order(),
1897 c in any_room_id_and_space_room_order()
1898 ) {
1899 let (a_room_id, a_order) = a;
1900 let (b_room_id, b_order) = b;
1901 let (c_room_id, c_order) = c;
1902
1903 let a = (a_room_id.as_ref(), a_order.as_deref());
1904 let b = (b_room_id.as_ref(), b_order.as_deref());
1905 let c = (c_room_id.as_ref(), c_order.as_deref());
1906
1907 let ab = compare_top_level_space_rooms(a, b);
1908 let bc = compare_top_level_space_rooms(b, c);
1909 let ac = compare_top_level_space_rooms(a, c);
1910
1911 if ab == Ordering::Less && bc == Ordering::Less {
1912 prop_assert_eq!(ac, Ordering::Less);
1913 }
1914
1915 if ab == Ordering::Equal && bc == Ordering::Equal {
1916 prop_assert_eq!(ac, Ordering::Equal);
1917 }
1918
1919 if ab == Ordering::Greater && bc == Ordering::Greater {
1920 prop_assert_eq!(ac, Ordering::Greater);
1921 }
1922 }
1923 }
1924}