diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 848d5544c16..4f4e2b0bf38 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -1152,17 +1152,6 @@ impl Client { let alias = RoomAliasId::parse(alias)?; self.inner.is_room_alias_available(&alias).await.map_err(Into::into) } - - /// Creates a new room alias associated with the provided room id. - pub async fn create_room_alias( - &self, - room_alias: String, - room_id: String, - ) -> Result<(), ClientError> { - let room_alias = RoomAliasId::parse(room_alias)?; - let room_id = RoomId::parse(room_id)?; - self.inner.create_room_alias(&room_alias, &room_id).await.map_err(Into::into) - } } #[matrix_sdk_ffi_macros::export(callback_interface)] diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index 9d793b07020..5ce1bf48e33 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -155,6 +155,12 @@ impl From for ClientError { } } +impl From for ClientError { + fn from(_: NotYetImplemented) -> Self { + Self::new("This functionality is not implemented yet.") + } +} + /// Bindings version of the sdk type replacing OwnedUserId/DeviceIds with simple /// String. /// diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index fd0f6aef3fa..065f1c6e54e 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -20,7 +20,8 @@ use ruma::{ call::notify, room::{ avatar::ImageInfo as RumaAvatarImageInfo, - message::RoomMessageEventContentWithoutRelation, + history_visibility::HistoryVisibility as RumaHistoryVisibility, + join_rules::JoinRule as RumaJoinRule, message::RoomMessageEventContentWithoutRelation, power_levels::RoomPowerLevels as RumaPowerLevels, MediaSource, }, AnyMessageLikeEventContent, AnySyncTimelineEvent, TimelineEventType, @@ -33,7 +34,8 @@ use tracing::error; use super::RUNTIME; use crate::{ chunk_iterator::ChunkIterator, - error::{ClientError, MediaInfoError, RoomError}, + client::{JoinRule, RoomVisibility}, + error::{ClientError, MediaInfoError, NotYetImplemented, RoomError}, event::{MessageLikeEventType, RoomMessageEventMessageType, StateEventType}, identity_status_change::IdentityStatusChange, room_info::RoomInfo, @@ -911,6 +913,45 @@ impl Room { room_event_cache.clear().await?; Ok(()) } + + /// Update the canonical alias of the room. + /// + /// Note that publishing the alias in the room directory is done separately. + pub async fn update_canonical_alias( + &self, + new_alias: Option, + ) -> Result<(), ClientError> { + let new_alias = new_alias.map(TryInto::try_into).transpose()?; + self.inner.update_canonical_alias(new_alias).await.map_err(Into::into) + } + + /// Enable End-to-end encryption in this room. + pub async fn enable_encryption(&self) -> Result<(), ClientError> { + self.inner.enable_encryption().await.map_err(Into::into) + } + + /// Update room history visibility for this room. + pub async fn update_history_visibility( + &self, + visibility: RoomHistoryVisibility, + ) -> Result<(), ClientError> { + let visibility: RumaHistoryVisibility = visibility.try_into()?; + self.inner.update_room_history_visibility(visibility).await.map_err(Into::into) + } + + /// Update the join rule for this room. + pub async fn update_join_rules(&self, new_rule: JoinRule) -> Result<(), ClientError> { + let new_rule: RumaJoinRule = new_rule.try_into()?; + self.inner.update_join_rule(new_rule).await.map_err(Into::into) + } + + /// Update the room's visibility in the room directory. + pub async fn update_room_visibility( + &self, + visibility: RoomVisibility, + ) -> Result<(), ClientError> { + self.inner.update_room_visibility(visibility.into()).await.map_err(Into::into) + } } /// Generates a `matrix.to` permalink to the given room alias. @@ -1144,3 +1185,62 @@ impl TryFrom for SdkComposerDraftType { Ok(draft_type) } } + +#[derive(Debug, Clone, uniffi::Enum)] +pub enum RoomHistoryVisibility { + /// Previous events are accessible to newly joined members from the point + /// they were invited onwards. + /// + /// Events stop being accessible when the member's state changes to + /// something other than *invite* or *join*. + Invited, + + /// Previous events are accessible to newly joined members from the point + /// they joined the room onwards. + /// Events stop being accessible when the member's state changes to + /// something other than *join*. + Joined, + + /// Previous events are always accessible to newly joined members. + /// + /// All events in the room are accessible, even those sent when the member + /// was not a part of the room. + Shared, + + /// All events while this is the `HistoryVisibility` value may be shared by + /// any participating homeserver with anyone, regardless of whether they + /// have ever joined the room. + WorldReadable, + + /// A custom visibility value. + Custom { value: String }, +} + +impl TryFrom for RoomHistoryVisibility { + type Error = NotYetImplemented; + fn try_from(value: RumaHistoryVisibility) -> Result { + match value { + RumaHistoryVisibility::Invited => Ok(RoomHistoryVisibility::Invited), + RumaHistoryVisibility::Shared => Ok(RoomHistoryVisibility::Shared), + RumaHistoryVisibility::WorldReadable => Ok(RoomHistoryVisibility::WorldReadable), + RumaHistoryVisibility::Joined => Ok(RoomHistoryVisibility::Joined), + RumaHistoryVisibility::_Custom(_) => { + Ok(RoomHistoryVisibility::Custom { value: value.to_string() }) + } + _ => Err(NotYetImplemented), + } + } +} + +impl TryFrom for RumaHistoryVisibility { + type Error = NotYetImplemented; + fn try_from(value: RoomHistoryVisibility) -> Result { + match value { + RoomHistoryVisibility::Invited => Ok(RumaHistoryVisibility::Invited), + RoomHistoryVisibility::Shared => Ok(RumaHistoryVisibility::Shared), + RoomHistoryVisibility::Joined => Ok(RumaHistoryVisibility::Joined), + RoomHistoryVisibility::WorldReadable => Ok(RumaHistoryVisibility::WorldReadable), + RoomHistoryVisibility::Custom { .. } => Err(NotYetImplemented), + } + } +} diff --git a/bindings/matrix-sdk-ffi/src/room_info.rs b/bindings/matrix-sdk-ffi/src/room_info.rs index 1b0f27e7114..970fae83d15 100644 --- a/bindings/matrix-sdk-ffi/src/room_info.rs +++ b/bindings/matrix-sdk-ffi/src/room_info.rs @@ -6,7 +6,7 @@ use tracing::warn; use crate::{ client::JoinRule, notification_settings::RoomNotificationMode, - room::{Membership, RoomHero}, + room::{Membership, RoomHero, RoomHistoryVisibility}, room_member::RoomMember, }; @@ -60,6 +60,8 @@ pub struct RoomInfo { pinned_event_ids: Vec, /// The join rule for this room, if known. join_rule: Option, + /// The history visibility for this room, if known. + history_visibility: Option, } impl RoomInfo { @@ -128,6 +130,7 @@ impl RoomInfo { num_unread_mentions: room.num_unread_mentions(), pinned_event_ids, join_rule: join_rule.ok(), + history_visibility: room.history_visibility().and_then(|h| h.try_into().ok()), }) } } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 2f70e6b8822..5a555cc9388 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -45,7 +45,7 @@ use ruma::{ api::{ client::{ account::whoami, - alias::{create_alias, get_alias}, + alias::{create_alias, delete_alias, get_alias}, device::{delete_devices, get_devices, update_device}, directory::{get_public_rooms, get_public_rooms_filtered}, discovery::{ @@ -1210,13 +1210,20 @@ impl Client { } } - /// Creates a new room alias associated with a room. + /// Adds a new room alias associated with a room to the room directory. pub async fn create_room_alias(&self, alias: &RoomAliasId, room_id: &RoomId) -> HttpResult<()> { let request = create_alias::v3::Request::new(alias.to_owned(), room_id.to_owned()); self.send(request, None).await?; Ok(()) } + /// Removes a room alias from the room directory. + pub async fn remove_room_alias(&self, alias: &RoomAliasId) -> HttpResult<()> { + let request = delete_alias::v3::Request::new(alias.to_owned()); + self.send(request, None).await?; + Ok(()) + } + /// Update the homeserver from the login response well-known if needed. /// /// # Arguments @@ -3156,7 +3163,7 @@ pub(crate) mod tests { let server = MatrixMockServer::new().await; let client = server.client_builder().build().await; - server.mock_create_room_alias().ok().expect(1).mount().await; + server.mock_room_directory_create_room_alias().ok().expect(1).mount().await; let ret = client .create_room_alias( diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index 438a28c4091..1927a67e675 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -150,6 +150,9 @@ mod member; mod messages; pub mod power_levels; +/// Contains all the functionality for modifying the privacy settings in a room. +pub mod privacy_settings; + /// A struct containing methods that are common for Joined, Invited and Left /// Rooms #[derive(Debug, Clone)] diff --git a/crates/matrix-sdk/src/room/privacy_settings.rs b/crates/matrix-sdk/src/room/privacy_settings.rs new file mode 100644 index 00000000000..10e33bbe68e --- /dev/null +++ b/crates/matrix-sdk/src/room/privacy_settings.rs @@ -0,0 +1,440 @@ +use ruma::{ + api::client::{directory::set_room_visibility, room::Visibility, state::send_state_event}, + assign, + events::{ + room::{ + canonical_alias::RoomCanonicalAliasEventContent, + history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent}, + join_rules::{JoinRule, RoomJoinRulesEventContent}, + }, + EmptyStateKey, + }, + OwnedRoomAliasId, RoomAliasId, +}; + +use crate::{Error, Result, Room}; + +impl Room { + /// Update the canonical alias of the room. + /// + /// Note that publishing the alias in the room directory is done separately. + pub async fn update_canonical_alias(&self, new_alias: Option) -> Result<()> { + // Create a new alias event combining both the new and previous values + let content = assign!( + RoomCanonicalAliasEventContent::new(), + { alias: new_alias, alt_aliases: self.alt_aliases() } + ); + + // Send the state event + let request = send_state_event::v3::Request::new( + self.room_id().to_owned(), + &EmptyStateKey, + &content, + )?; + self.client.send(request, None).await?; + + Ok(()) + } + + /// Update room history visibility for this room. + pub async fn update_room_history_visibility(&self, new_value: HistoryVisibility) -> Result<()> { + let request = send_state_event::v3::Request::new( + self.room_id().to_owned(), + &EmptyStateKey, + &RoomHistoryVisibilityEventContent::new(new_value), + )?; + self.client.send(request, None).await?; + Ok(()) + } + + /// Update the join rule for this room. + pub async fn update_join_rule(&self, new_rule: JoinRule) -> Result<()> { + let request = send_state_event::v3::Request::new( + self.room_id().to_owned(), + &EmptyStateKey, + &RoomJoinRulesEventContent::new(new_rule), + )?; + self.client.send(request, None).await?; + Ok(()) + } + + /// Update the room alias of this room and publish it in the room directory. + pub async fn update_and_publish_room_alias(&self, alias: &RoomAliasId) -> Result<()> { + let previous_alias = self.canonical_alias(); + + // First, publish the new alias in the room directory + self.client.create_room_alias(alias, self.room_id()).await?; + + // Remove the previous alias from the directory if needed + if let Some(previous_alias) = previous_alias { + if !self.client.is_room_alias_available(&previous_alias).await? { + self.client.remove_room_alias(&previous_alias).await?; + } + } + + // Then update the canonical alias in the room + self.update_canonical_alias(Some(alias.to_owned())).await?; + + Ok(()) + } + + /// Remove the room alias from this room and the room directory. + pub async fn remove_and_delist_room_alias(&self) -> Result<()> { + let Some(previous_alias) = self.canonical_alias() else { + return Err(Error::InsufficientData); + }; + + self.update_canonical_alias(None).await?; + + self.client.remove_room_alias(&previous_alias).await?; + Ok(()) + } + + /// Update the visibility for this room in the room directory. + pub async fn update_room_visibility(&self, visibility: Visibility) -> Result<()> { + let request = set_room_visibility::v3::Request::new(self.room_id().to_owned(), visibility); + + self.client.send(request, None).await?; + + Ok(()) + } +} + +#[cfg(all(test, not(target_arch = "wasm32")))] +mod tests { + use matrix_sdk_test::{async_test, JoinedRoomBuilder, StateTestEvent}; + use ruma::{ + api::client::room::Visibility, + event_id, + events::{ + room::{history_visibility::HistoryVisibility, join_rules::JoinRule}, + StateEventType, + }, + owned_room_alias_id, room_id, + }; + + use crate::test_utils::mocks::MatrixMockServer; + + #[async_test] + async fn test_update_canonical_alias_with_some_value() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_canonical_alias(Some(room_alias.clone())).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_canonical_alias_with_no_value() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let ret = room.update_canonical_alias(None).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_to_room_directory() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + server.mock_room_directory_create_room_alias().ok().mock_once().mount().await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_to_room_directory_with_previous_alias() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + // First we create a new room alias association in the room directory + server.mock_room_directory_create_room_alias().ok().mock_once().mount().await; + + // Then we check if a previous room alias exists + server + .mock_room_directory_resolve_alias() + .ok(room_id.as_str(), Vec::new()) + .mock_once() + .mount() + .await; + // It exists, so we remove it + server.mock_room_directory_remove_room_alias().ok().mock_once().mount().await; + + // Finally, the new state event will be sent + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_when_create_alias_fails() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + // If creating the room alias association fails + server.mock_room_directory_create_room_alias().error500().mock_once().mount().await; + + // Everything else fails + server + .mock_room_directory_resolve_alias() + .ok(room_id.as_str(), Vec::new()) + .never() + .mount() + .await; + server.mock_room_directory_remove_room_alias().ok().never().mount().await; + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .never() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_err()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_when_resolve_room_fails() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + // First the room alias association will be created + server.mock_room_directory_create_room_alias().ok().mock_once().mount().await; + + // If resolving the alias fails + server.mock_room_directory_resolve_alias().error500().mock_once().mount().await; + + // Everything after it fails too + server.mock_room_directory_remove_room_alias().ok().never().mount().await; + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .never() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_err()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_with_previous_alias_if_not_resolved() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + // First the room alias association will be created + server.mock_room_directory_create_room_alias().ok().mock_once().mount().await; + + // If the alias could not be resolved + server.mock_room_directory_resolve_alias().not_found().mock_once().mount().await; + + // Removal is not called + server.mock_room_directory_remove_room_alias().ok().never().mount().await; + + // We'll still send the canonical alias state event + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_and_publish_canonical_alias_if_sending_canonical_alias_event_fails() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + // First the room alias association will be created + server.mock_room_directory_create_room_alias().ok().mock_once().mount().await; + + // Then we check if a previous room alias exists + server + .mock_room_directory_resolve_alias() + .ok(room_id.as_str(), Vec::new()) + .mock_once() + .mount() + .await; + + // It exists, so we remove it + server.mock_room_directory_remove_room_alias().ok().mock_once().mount().await; + + // Then we try to send a new canonical alias state event and it fails + server + .mock_room_send_state() + .for_type(StateEventType::RoomCanonicalAlias) + .error500() + .mock_once() + .mount() + .await; + + let room_alias = owned_room_alias_id!("#a:b.c"); + let ret = room.update_and_publish_room_alias(&room_alias).await; + assert!(ret.is_err()); + } + + #[async_test] + async fn test_remove_and_delist_room_alias() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let joined_room_builder = + JoinedRoomBuilder::new(room_id).add_state_event(StateTestEvent::Alias); + let room = server.sync_room(&client, joined_room_builder).await; + + server.mock_room_send_state().ok(event_id!("$a:b.c")).mock_once().mount().await; + server.mock_room_directory_remove_room_alias().ok().mock_once().mount().await; + + let ret = room.remove_and_delist_room_alias().await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_remove_and_delist_room_alias_with_no_previous_alias() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + // The endpoints are never even called + server.mock_room_send_state().error500().expect(0).mount().await; + server.mock_room_directory_remove_room_alias().ok().expect(0).mount().await; + + let ret = room.remove_and_delist_room_alias().await; + assert!(ret.is_err()); + } + + #[async_test] + async fn test_update_room_history_visibility() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server + .mock_room_send_state() + .for_type(StateEventType::RoomHistoryVisibility) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let ret = room.update_room_history_visibility(HistoryVisibility::Joined).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_join_rule() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server + .mock_room_send_state() + .for_type(StateEventType::RoomJoinRules) + .ok(event_id!("$a:b.c")) + .mock_once() + .mount() + .await; + + let ret = room.update_join_rule(JoinRule::Public).await; + assert!(ret.is_ok()); + } + + #[async_test] + async fn test_update_room_visibility() { + let server = MatrixMockServer::new().await; + let client = server.client_builder().build().await; + + let room_id = room_id!("!a:b.c"); + let room = server.sync_joined_room(&client, room_id).await; + + server.mock_room_directory_set_room_visibility().ok().mock_once().mount().await; + + let ret = room.update_room_visibility(Visibility::Private).await; + assert!(ret.is_ok()); + } +} diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs index 74d25a5ac01..3bc334568ee 100644 --- a/crates/matrix-sdk/src/test_utils/mocks.rs +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -506,19 +506,113 @@ impl MatrixMockServer { } /// Create a prebuilt mock for resolving room aliases. + /// + /// # Examples + /// + /// ``` + /// # tokio_test::block_on(async { + /// use matrix_sdk::{ + /// ruma::{owned_room_id, room_alias_id}, + /// test_utils::mocks::MatrixMockServer, + /// }; + /// use ruma::api::client::room::Visibility; + /// + /// let mock_server = MatrixMockServer::new().await; + /// let client = mock_server.client_builder().build().await; + /// + /// mock_server + /// .mock_room_directory_resolve_alias() + /// .ok("!a:b.c", Vec::new()) + /// .mock_once() + /// .mount() + /// .await; + /// + /// let res = client + /// .resolve_room_alias(room_alias_id!("#a:b.c")) + /// .await + /// .expect("We should be able to resolve the room alias"); + /// assert_eq!(res.room_id, owned_room_id!("!a:b.c")); + /// # anyhow::Ok(()) }); + /// ``` pub fn mock_room_directory_resolve_alias(&self) -> MockEndpoint<'_, ResolveRoomAliasEndpoint> { let mock = Mock::given(method("GET")).and(path_regex(r"/_matrix/client/v3/directory/room/.*")); MockEndpoint { mock, server: &self.server, endpoint: ResolveRoomAliasEndpoint } } - /// Create a prebuilt mock for creating room aliases. - pub fn mock_create_room_alias(&self) -> MockEndpoint<'_, CreateRoomAliasEndpoint> { + /// Create a prebuilt mock for publishing room aliases in the room + /// directory. + /// + /// # Examples + /// + /// ``` + /// # tokio_test::block_on(async { + /// use matrix_sdk::{ + /// ruma::{room_alias_id, room_id}, + /// test_utils::mocks::MatrixMockServer, + /// }; + /// use ruma::api::client::room::Visibility; + /// + /// let mock_server = MatrixMockServer::new().await; + /// let client = mock_server.client_builder().build().await; + /// + /// mock_server + /// .mock_room_directory_create_room_alias() + /// .ok() + /// .mock_once() + /// .mount() + /// .await; + /// + /// client + /// .create_room_alias(room_alias_id!("#a:b.c"), room_id!("!a:b.c")) + /// .await + /// .expect("We should be able to create a room alias"); + /// # anyhow::Ok(()) }); + /// ``` + pub fn mock_room_directory_create_room_alias( + &self, + ) -> MockEndpoint<'_, CreateRoomAliasEndpoint> { let mock = Mock::given(method("PUT")).and(path_regex(r"/_matrix/client/v3/directory/room/.*")); MockEndpoint { mock, server: &self.server, endpoint: CreateRoomAliasEndpoint } } + /// Create a prebuilt mock for removing room aliases from the room + /// directory. + /// + /// # Examples + /// + /// ``` + /// # tokio_test::block_on(async { + /// use matrix_sdk::{ + /// ruma::room_alias_id, test_utils::mocks::MatrixMockServer, + /// }; + /// use ruma::api::client::room::Visibility; + /// + /// let mock_server = MatrixMockServer::new().await; + /// let client = mock_server.client_builder().build().await; + /// + /// mock_server + /// .mock_room_directory_remove_room_alias() + /// .ok() + /// .mock_once() + /// .mount() + /// .await; + /// + /// client + /// .remove_room_alias(room_alias_id!("#a:b.c")) + /// .await + /// .expect("We should be able to remove the room alias"); + /// # anyhow::Ok(()) }); + /// ``` + pub fn mock_room_directory_remove_room_alias( + &self, + ) -> MockEndpoint<'_, RemoveRoomAliasEndpoint> { + let mock = + Mock::given(method("DELETE")).and(path_regex(r"/_matrix/client/v3/directory/room/.*")); + MockEndpoint { mock, server: &self.server, endpoint: RemoveRoomAliasEndpoint } + } + /// Create a prebuilt mock for listing public rooms. /// /// # Examples @@ -562,6 +656,43 @@ impl MatrixMockServer { MockEndpoint { mock, server: &self.server, endpoint: PublicRoomsEndpoint } } + /// Create a prebuilt mock for setting a room's visibility in the room + /// directory. + /// + /// # Examples + /// + /// ``` + /// # tokio_test::block_on(async { + /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer}; + /// use ruma::api::client::room::Visibility; + /// + /// let mock_server = MatrixMockServer::new().await; + /// let client = mock_server.client_builder().build().await; + /// + /// mock_server + /// .mock_room_directory_set_room_visibility() + /// .ok() + /// .mock_once() + /// .mount() + /// .await; + /// + /// let room = mock_server + /// .sync_joined_room(&client, room_id!("!room_id:localhost")) + /// .await; + /// + /// room.update_room_visibility(Visibility::Private) + /// .await + /// .expect("We should be able to update the room's visibility"); + /// # anyhow::Ok(()) }); + /// ``` + pub fn mock_room_directory_set_room_visibility( + &self, + ) -> MockEndpoint<'_, SetRoomVisibilityEndpoint> { + let mock = Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$")); + MockEndpoint { mock, server: &self.server, endpoint: SetRoomVisibilityEndpoint } + } + /// Create a prebuilt mock for fetching information about key storage /// backups. /// @@ -697,6 +828,11 @@ impl MatrixMock<'_> { Self { mock: self.mock.up_to_n_times(1).expect(1), ..self } } + /// Makes sure the endpoint is never reached. + pub fn never(self) -> Self { + Self { mock: self.mock.expect(0), ..self } + } + /// Specify an upper limit to the number of times you would like this /// [`MatrixMock`] to respond to incoming requests that satisfy the /// conditions imposed by your matchers. @@ -1023,7 +1159,7 @@ impl<'a> MockEndpoint<'a, RoomSendEndpoint> { /// /// let response = room.client().send(r, None).await.unwrap(); /// // The delayed `m.room.message` event type should be mocked by the server. - /// assert_eq!("$some_id", response.delay_id); + /// assert_eq!("$some_id", response.delay_id); /// # anyhow::Ok(()) }); /// ``` pub fn with_delay(self, delay: Duration) -> Self { @@ -1632,6 +1768,17 @@ impl<'a> MockEndpoint<'a, CreateRoomAliasEndpoint> { } } +/// A prebuilt mock for removing a room alias. +pub struct RemoveRoomAliasEndpoint; + +impl<'a> MockEndpoint<'a, RemoveRoomAliasEndpoint> { + /// Returns a data endpoint for removing a room alias. + pub fn ok(self) -> MatrixMock<'a> { + let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({}))); + MatrixMock { server: self.server, mock } + } +} + /// A prebuilt mock for paginating the public room list. pub struct PublicRoomsEndpoint; @@ -1685,6 +1832,17 @@ impl<'a> MockEndpoint<'a, PublicRoomsEndpoint> { } } +/// A prebuilt mock for setting the room's visibility in the room directory. +pub struct SetRoomVisibilityEndpoint; + +impl<'a> MockEndpoint<'a, SetRoomVisibilityEndpoint> { + /// Returns an endpoint that updates the room's visibility. + pub fn ok(self) -> MatrixMock<'a> { + let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({}))); + MatrixMock { server: self.server, mock } + } +} + /// A prebuilt mock for `GET room_keys/version`: storage ("backup") of room /// keys. pub struct RoomKeysVersionEndpoint;