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