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