#![allow(missing_debug_implementations)]
use std::{
collections::BTreeMap,
sync::{Arc, Mutex, atomic::AtomicU32},
};
use js_int::UInt;
use matrix_sdk_base::deserialized_responses::TimelineEvent;
#[cfg(feature = "experimental-element-recent-emojis")]
use matrix_sdk_base::recent_emojis::RecentEmojisContent;
use matrix_sdk_test::{
InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder,
SyncResponseBuilder, test_json,
};
use percent_encoding::{AsciiSet, CONTROLS};
use ruma::{
DeviceId, EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId,
OwnedOneTimeKeyId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
api::client::{
profile::{ProfileFieldName, ProfileFieldValue},
receipt::create_receipt::v3::ReceiptType,
room::Visibility,
sync::sync_events::v5,
threads::get_thread_subscriptions_changes::unstable::{
ThreadSubscription, ThreadUnsubscription,
},
},
device_id,
directory::PublicRoomsChunk,
encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
events::{
AnyStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, GlobalAccountDataEventType,
MessageLikeEventType, RoomAccountDataEventType, StateEventType, receipt::ReceiptThread,
room::member::RoomMemberEvent,
},
media::Method,
push::RuleKind,
serde::Raw,
time::Duration,
};
use serde::{Deserialize, Serialize};
use serde_json::{Value, from_value, json};
use tokio::sync::oneshot::{self, Receiver};
use wiremock::{
Mock, MockBuilder, MockGuard, MockServer, Request, Respond, ResponseTemplate, Times,
matchers::{
body_json, body_partial_json, header, method, path, path_regex, query_param,
query_param_is_missing,
},
};
#[cfg(feature = "e2e-encryption")]
pub mod encryption;
pub mod oauth;
use super::client::MockClientBuilder;
use crate::{Client, OwnedServerName, Room, SlidingSyncBuilder, room::IncludeRelations};
#[derive(Debug, Default)]
struct Keys {
device: BTreeMap<OwnedUserId, BTreeMap<String, Raw<DeviceKeys>>>,
master: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
self_signing: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
user_signing: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
one_time_keys: BTreeMap<
OwnedUserId,
BTreeMap<OwnedDeviceId, BTreeMap<OwnedOneTimeKeyId, Raw<OneTimeKey>>>,
>,
}
pub struct MatrixMockServer {
server: MockServer,
sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
keys: Arc<Mutex<Keys>>,
token_to_user_id_map: Arc<Mutex<BTreeMap<String, OwnedUserId>>>,
token_counter: AtomicU32,
}
impl MatrixMockServer {
pub async fn new() -> Self {
let server = MockServer::start().await;
let keys: Arc<Mutex<Keys>> = Default::default();
Self {
server,
sync_response_builder: Default::default(),
keys,
token_to_user_id_map: Default::default(),
token_counter: AtomicU32::new(0),
}
}
pub fn from_server(server: MockServer) -> Self {
let keys: Arc<Mutex<Keys>> = Default::default();
Self {
server,
sync_response_builder: Default::default(),
keys,
token_to_user_id_map: Default::default(),
token_counter: AtomicU32::new(0),
}
}
pub fn client_builder(&self) -> MockClientBuilder {
MockClientBuilder::new(Some(&self.server.uri()))
}
pub fn server(&self) -> &MockServer {
&self.server
}
pub fn uri(&self) -> String {
self.server.uri()
}
pub fn oauth(&self) -> oauth::OAuthMockServer<'_> {
oauth::OAuthMockServer::new(self)
}
fn mock_endpoint<T>(&self, mock: MockBuilder, endpoint: T) -> MockEndpoint<'_, T> {
MockEndpoint::new(&self.server, mock, endpoint)
}
pub async fn sync_room(&self, client: &Client, room_data: impl Into<AnyRoomBuilder>) -> Room {
let any_room = room_data.into();
let room_id = any_room.room_id().to_owned();
self.mock_sync()
.ok_and_run(client, move |builder| match any_room {
AnyRoomBuilder::Invited(invited) => {
builder.add_invited_room(invited);
}
AnyRoomBuilder::Joined(joined) => {
builder.add_joined_room(joined);
}
AnyRoomBuilder::Left(left) => {
builder.add_left_room(left);
}
AnyRoomBuilder::Knocked(knocked) => {
builder.add_knocked_room(knocked);
}
})
.await;
client.get_room(&room_id).expect("look at me, the room is known now")
}
pub async fn sync_joined_room(&self, client: &Client, room_id: &RoomId) -> Room {
self.sync_room(client, JoinedRoomBuilder::new(room_id)).await
}
pub async fn verify_and_reset(&self) {
self.server.verify().await;
self.server.reset().await;
}
}
impl MatrixMockServer {
pub fn mock_sync(&self) -> MockEndpoint<'_, SyncEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/sync"));
self.mock_endpoint(
mock,
SyncEndpoint { sync_response_builder: self.sync_response_builder.clone() },
)
}
pub fn mock_sliding_sync(&self) -> MockEndpoint<'_, SlidingSyncEndpoint> {
let mock = Mock::given(method("POST"))
.and(path("/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"));
self.mock_endpoint(mock, SlidingSyncEndpoint)
}
pub fn mock_room_join(&self, room_id: &RoomId) -> MockEndpoint<'_, JoinRoomEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(format!("^/_matrix/client/v3/rooms/{room_id}/join")));
self.mock_endpoint(mock, JoinRoomEndpoint { room_id: room_id.to_owned() })
}
pub fn mock_room_send(&self) -> MockEndpoint<'_, RoomSendEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/send/.*".to_owned()));
self.mock_endpoint(mock, RoomSendEndpoint)
}
pub fn mock_room_send_state(&self) -> MockEndpoint<'_, RoomSendStateEndpoint> {
let mock =
Mock::given(method("PUT")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/.*/.*"));
self.mock_endpoint(mock, RoomSendStateEndpoint::default()).expect_default_access_token()
}
pub fn mock_room_state_encryption(&self) -> MockEndpoint<'_, EncryptionStateEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
self.mock_endpoint(mock, EncryptionStateEndpoint).expect_default_access_token()
}
pub fn mock_set_room_state_encryption(&self) -> MockEndpoint<'_, SetEncryptionStateEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
self.mock_endpoint(mock, SetEncryptionStateEndpoint).expect_default_access_token()
}
pub fn mock_room_redact(&self) -> MockEndpoint<'_, RoomRedactEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/redact/.*?/.*?"));
self.mock_endpoint(mock, RoomRedactEndpoint).expect_default_access_token()
}
pub fn mock_room_event(&self) -> MockEndpoint<'_, RoomEventEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, RoomEventEndpoint { room: None, match_event_id: false })
.expect_default_access_token()
}
pub fn mock_room_event_context(&self) -> MockEndpoint<'_, RoomEventContextEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, RoomEventContextEndpoint { room: None, match_event_id: false })
.expect_default_access_token()
}
pub fn mock_room_messages(&self) -> MockEndpoint<'_, RoomMessagesEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/messages$"));
self.mock_endpoint(mock, RoomMessagesEndpoint).expect_default_access_token()
}
pub fn mock_upload(&self) -> MockEndpoint<'_, UploadEndpoint> {
let mock = Mock::given(method("POST")).and(path("/_matrix/media/v3/upload"));
self.mock_endpoint(mock, UploadEndpoint)
}
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/.*"));
self.mock_endpoint(mock, ResolveRoomAliasEndpoint)
}
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/.*"));
self.mock_endpoint(mock, CreateRoomAliasEndpoint)
}
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/.*"));
self.mock_endpoint(mock, RemoveRoomAliasEndpoint)
}
pub fn mock_public_rooms(&self) -> MockEndpoint<'_, PublicRoomsEndpoint> {
let mock = Mock::given(method("POST")).and(path_regex(r"/_matrix/client/v3/publicRooms"));
self.mock_endpoint(mock, PublicRoomsEndpoint)
}
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/.*$"));
self.mock_endpoint(mock, SetRoomVisibilityEndpoint)
}
pub fn mock_room_directory_get_room_visibility(
&self,
) -> MockEndpoint<'_, GetRoomVisibilityEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$"));
self.mock_endpoint(mock, GetRoomVisibilityEndpoint)
}
pub fn mock_room_keys_version(&self) -> MockEndpoint<'_, RoomKeysVersionEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"_matrix/client/v3/room_keys/version"));
self.mock_endpoint(mock, RoomKeysVersionEndpoint).expect_default_access_token()
}
pub fn mock_add_room_keys_version(&self) -> MockEndpoint<'_, AddRoomKeysVersionEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex(r"_matrix/client/v3/room_keys/version"));
self.mock_endpoint(mock, AddRoomKeysVersionEndpoint).expect_default_access_token()
}
pub fn mock_delete_room_keys_version(&self) -> MockEndpoint<'_, DeleteRoomKeysVersionEndpoint> {
let mock = Mock::given(method("DELETE"))
.and(path_regex(r"_matrix/client/v3/room_keys/version/[^/]*"));
self.mock_endpoint(mock, DeleteRoomKeysVersionEndpoint).expect_default_access_token()
}
pub fn mock_send_to_device(&self) -> MockEndpoint<'_, SendToDeviceEndpoint> {
let mock =
Mock::given(method("PUT")).and(path_regex(r"^/_matrix/client/v3/sendToDevice/.*/.*"));
self.mock_endpoint(mock, SendToDeviceEndpoint).expect_default_access_token()
}
pub fn mock_get_members(&self) -> MockEndpoint<'_, GetRoomMembersEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/members$"));
self.mock_endpoint(mock, GetRoomMembersEndpoint)
}
pub fn mock_invite_user_by_id(&self) -> MockEndpoint<'_, InviteUserByIdEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/invite$"));
self.mock_endpoint(mock, InviteUserByIdEndpoint)
}
pub fn mock_kick_user(&self) -> MockEndpoint<'_, KickUserEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/kick"));
self.mock_endpoint(mock, KickUserEndpoint)
}
pub fn mock_ban_user(&self) -> MockEndpoint<'_, BanUserEndpoint> {
let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/ban"));
self.mock_endpoint(mock, BanUserEndpoint)
}
pub fn mock_versions(&self) -> MockEndpoint<'_, VersionsEndpoint> {
let mock = Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/versions"));
self.mock_endpoint(mock, VersionsEndpoint)
}
pub fn mock_room_summary(&self) -> MockEndpoint<'_, RoomSummaryEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex(r"^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary"));
self.mock_endpoint(mock, RoomSummaryEndpoint)
}
pub fn mock_set_room_pinned_events(&self) -> MockEndpoint<'_, SetRoomPinnedEventsEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.room.pinned_events/.*?"));
self.mock_endpoint(mock, SetRoomPinnedEventsEndpoint).expect_default_access_token()
}
pub fn mock_who_am_i(&self) -> MockEndpoint<'_, WhoAmIEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/account/whoami"));
self.mock_endpoint(mock, WhoAmIEndpoint).expect_default_access_token()
}
pub fn mock_upload_keys(&self) -> MockEndpoint<'_, UploadKeysEndpoint> {
let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/keys/upload"));
self.mock_endpoint(mock, UploadKeysEndpoint).expect_default_access_token()
}
pub fn mock_query_keys(&self) -> MockEndpoint<'_, QueryKeysEndpoint> {
let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/keys/query"));
self.mock_endpoint(mock, QueryKeysEndpoint).expect_default_access_token()
}
pub fn mock_well_known(&self) -> MockEndpoint<'_, WellKnownEndpoint> {
let mock = Mock::given(method("GET")).and(path_regex(r"^/.well-known/matrix/client"));
self.mock_endpoint(mock, WellKnownEndpoint)
}
pub fn mock_upload_cross_signing_keys(
&self,
) -> MockEndpoint<'_, UploadCrossSigningKeysEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(r"^/_matrix/client/v3/keys/device_signing/upload"));
self.mock_endpoint(mock, UploadCrossSigningKeysEndpoint).expect_default_access_token()
}
pub fn mock_upload_cross_signing_signatures(
&self,
) -> MockEndpoint<'_, UploadCrossSigningSignaturesEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(r"^/_matrix/client/v3/keys/signatures/upload"));
self.mock_endpoint(mock, UploadCrossSigningSignaturesEndpoint).expect_default_access_token()
}
pub fn mock_room_leave(&self) -> MockEndpoint<'_, RoomLeaveEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/leave"));
self.mock_endpoint(mock, RoomLeaveEndpoint).expect_default_access_token()
}
pub fn mock_room_forget(&self) -> MockEndpoint<'_, RoomForgetEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/forget"));
self.mock_endpoint(mock, RoomForgetEndpoint).expect_default_access_token()
}
pub fn mock_logout(&self) -> MockEndpoint<'_, LogoutEndpoint> {
let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/logout"));
self.mock_endpoint(mock, LogoutEndpoint).expect_default_access_token()
}
pub fn mock_room_threads(&self) -> MockEndpoint<'_, RoomThreadsEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v1/rooms/.*/threads$"));
self.mock_endpoint(mock, RoomThreadsEndpoint).expect_default_access_token()
}
pub fn mock_room_relations(&self) -> MockEndpoint<'_, RoomRelationsEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, RoomRelationsEndpoint::default()).expect_default_access_token()
}
#[cfg(feature = "experimental-element-recent-emojis")]
pub fn mock_get_recent_emojis(&self) -> MockEndpoint<'_, GetRecentEmojisEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, GetRecentEmojisEndpoint).expect_default_access_token()
}
#[cfg(feature = "experimental-element-recent-emojis")]
pub fn mock_add_recent_emojis(&self) -> MockEndpoint<'_, UpdateRecentEmojisEndpoint> {
let mock = Mock::given(method("PUT"));
self.mock_endpoint(mock, UpdateRecentEmojisEndpoint::new()).expect_default_access_token()
}
#[cfg(feature = "e2e-encryption")]
pub fn mock_get_default_secret_storage_key(
&self,
) -> MockEndpoint<'_, GetDefaultSecretStorageKeyEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, GetDefaultSecretStorageKeyEndpoint).expect_default_access_token()
}
#[cfg(feature = "e2e-encryption")]
pub fn mock_get_secret_storage_key(&self) -> MockEndpoint<'_, GetSecretStorageKeyEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, GetSecretStorageKeyEndpoint).expect_default_access_token()
}
#[cfg(feature = "e2e-encryption")]
pub fn mock_get_master_signing_key(&self) -> MockEndpoint<'_, GetMasterSigningKeyEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, GetMasterSigningKeyEndpoint).expect_default_access_token()
}
pub fn mock_send_receipt(
&self,
receipt_type: ReceiptType,
) -> MockEndpoint<'_, ReceiptEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(format!("^/_matrix/client/v3/rooms/.*/receipt/{receipt_type}/")));
self.mock_endpoint(mock, ReceiptEndpoint).expect_default_access_token()
}
pub fn mock_send_read_markers(&self) -> MockEndpoint<'_, ReadMarkersEndpoint> {
let mock = Mock::given(method("POST"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/read_markers"));
self.mock_endpoint(mock, ReadMarkersEndpoint).expect_default_access_token()
}
pub fn mock_set_room_account_data(
&self,
data_type: RoomAccountDataEventType,
) -> MockEndpoint<'_, RoomAccountDataEndpoint> {
let mock = Mock::given(method("PUT")).and(path_regex(format!(
"^/_matrix/client/v3/user/.*/rooms/.*/account_data/{data_type}"
)));
self.mock_endpoint(mock, RoomAccountDataEndpoint).expect_default_access_token()
}
pub fn mock_authenticated_media_config(
&self,
) -> MockEndpoint<'_, AuthenticatedMediaConfigEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/client/v1/media/config"));
self.mock_endpoint(mock, AuthenticatedMediaConfigEndpoint)
}
pub fn mock_media_config(&self) -> MockEndpoint<'_, MediaConfigEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/media/v3/config"));
self.mock_endpoint(mock, MediaConfigEndpoint)
}
pub fn mock_login(&self) -> MockEndpoint<'_, LoginEndpoint> {
let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/login"));
self.mock_endpoint(mock, LoginEndpoint)
}
pub fn mock_devices(&self) -> MockEndpoint<'_, DevicesEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/devices"));
self.mock_endpoint(mock, DevicesEndpoint).expect_default_access_token()
}
pub fn mock_get_device(&self) -> MockEndpoint<'_, GetDeviceEndpoint> {
let mock = Mock::given(method("GET")).and(path_regex("/_matrix/client/v3/devices/.*"));
self.mock_endpoint(mock, GetDeviceEndpoint).expect_default_access_token()
}
pub fn mock_user_directory(&self) -> MockEndpoint<'_, UserDirectoryEndpoint> {
let mock = Mock::given(method("POST"))
.and(path("/_matrix/client/v3/user_directory/search"))
.and(body_json(&*test_json::search_users::SEARCH_USERS_REQUEST));
self.mock_endpoint(mock, UserDirectoryEndpoint).expect_default_access_token()
}
pub fn mock_create_room(&self) -> MockEndpoint<'_, CreateRoomEndpoint> {
let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/createRoom"));
self.mock_endpoint(mock, CreateRoomEndpoint).expect_default_access_token()
}
pub fn mock_upgrade_room(&self) -> MockEndpoint<'_, UpgradeRoomEndpoint> {
let mock =
Mock::given(method("POST")).and(path_regex("/_matrix/client/v3/rooms/.*/upgrade"));
self.mock_endpoint(mock, UpgradeRoomEndpoint).expect_default_access_token()
}
pub fn mock_media_allocate(&self) -> MockEndpoint<'_, MediaAllocateEndpoint> {
let mock = Mock::given(method("POST")).and(path("/_matrix/media/v1/create"));
self.mock_endpoint(mock, MediaAllocateEndpoint)
}
pub fn mock_media_allocated_upload(
&self,
server_name: &str,
media_id: &str,
) -> MockEndpoint<'_, MediaAllocatedUploadEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path(format!("/_matrix/media/v3/upload/{server_name}/{media_id}")));
self.mock_endpoint(mock, MediaAllocatedUploadEndpoint)
}
pub fn mock_media_download(&self) -> MockEndpoint<'_, MediaDownloadEndpoint> {
let mock = Mock::given(method("GET")).and(path_regex("^/_matrix/media/v3/download/"));
self.mock_endpoint(mock, MediaDownloadEndpoint)
}
pub fn mock_media_thumbnail(
&self,
resize_method: Method,
width: u16,
height: u16,
animated: bool,
) -> MockEndpoint<'_, MediaThumbnailEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex("^/_matrix/media/v3/thumbnail/"))
.and(query_param("method", resize_method.as_str()))
.and(query_param("width", width.to_string()))
.and(query_param("height", height.to_string()))
.and(query_param("animated", animated.to_string()));
self.mock_endpoint(mock, MediaThumbnailEndpoint)
}
pub fn mock_authed_media_download(&self) -> MockEndpoint<'_, AuthedMediaDownloadEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex("^/_matrix/client/v1/media/download/"));
self.mock_endpoint(mock, AuthedMediaDownloadEndpoint).expect_default_access_token()
}
pub fn mock_authed_media_thumbnail(
&self,
resize_method: Method,
width: u16,
height: u16,
animated: bool,
) -> MockEndpoint<'_, AuthedMediaThumbnailEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex("^/_matrix/client/v1/media/thumbnail/"))
.and(query_param("method", resize_method.as_str()))
.and(query_param("width", width.to_string()))
.and(query_param("height", height.to_string()))
.and(query_param("animated", animated.to_string()));
self.mock_endpoint(mock, AuthedMediaThumbnailEndpoint).expect_default_access_token()
}
pub fn mock_room_get_thread_subscription(
&self,
) -> MockEndpoint<'_, RoomGetThreadSubscriptionEndpoint> {
let mock = Mock::given(method("GET"));
self.mock_endpoint(mock, RoomGetThreadSubscriptionEndpoint::default())
.expect_default_access_token()
}
pub fn mock_room_put_thread_subscription(
&self,
) -> MockEndpoint<'_, RoomPutThreadSubscriptionEndpoint> {
let mock = Mock::given(method("PUT"));
self.mock_endpoint(mock, RoomPutThreadSubscriptionEndpoint::default())
.expect_default_access_token()
}
pub fn mock_room_delete_thread_subscription(
&self,
) -> MockEndpoint<'_, RoomDeleteThreadSubscriptionEndpoint> {
let mock = Mock::given(method("DELETE"));
self.mock_endpoint(mock, RoomDeleteThreadSubscriptionEndpoint::default())
.expect_default_access_token()
}
pub fn mock_enable_push_rule(
&self,
kind: RuleKind,
rule_id: impl AsRef<str>,
) -> MockEndpoint<'_, EnablePushRuleEndpoint> {
let rule_id = rule_id.as_ref();
let mock = Mock::given(method("PUT")).and(path_regex(format!(
"^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}/enabled",
)));
self.mock_endpoint(mock, EnablePushRuleEndpoint).expect_default_access_token()
}
pub fn mock_set_push_rules_actions(
&self,
kind: RuleKind,
rule_id: PushRuleIdSpec<'_>,
) -> MockEndpoint<'_, SetPushRulesActionsEndpoint> {
let rule_id = rule_id.to_path();
let mock = Mock::given(method("PUT")).and(path_regex(format!(
"^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}/actions",
)));
self.mock_endpoint(mock, SetPushRulesActionsEndpoint).expect_default_access_token()
}
pub fn mock_set_push_rules(
&self,
kind: RuleKind,
rule_id: PushRuleIdSpec<'_>,
) -> MockEndpoint<'_, SetPushRulesEndpoint> {
let rule_id = rule_id.to_path();
let mock = Mock::given(method("PUT"))
.and(path_regex(format!("^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}$",)));
self.mock_endpoint(mock, SetPushRulesEndpoint).expect_default_access_token()
}
pub fn mock_delete_push_rules(
&self,
kind: RuleKind,
rule_id: PushRuleIdSpec<'_>,
) -> MockEndpoint<'_, DeletePushRulesEndpoint> {
let rule_id = rule_id.to_path();
let mock = Mock::given(method("DELETE"))
.and(path_regex(format!("^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}$",)));
self.mock_endpoint(mock, DeletePushRulesEndpoint).expect_default_access_token()
}
pub fn mock_federation_version(&self) -> MockEndpoint<'_, FederationVersionEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/federation/v1/version"));
self.mock_endpoint(mock, FederationVersionEndpoint)
}
pub fn mock_get_thread_subscriptions(
&self,
) -> MockEndpoint<'_, GetThreadSubscriptionsEndpoint> {
let mock = Mock::given(method("GET"))
.and(path_regex(r"^/_matrix/client/unstable/io.element.msc4308/thread_subscriptions$"));
self.mock_endpoint(mock, GetThreadSubscriptionsEndpoint::default())
.expect_default_access_token()
}
pub fn mock_get_hierarchy(&self) -> MockEndpoint<'_, GetHierarchyEndpoint> {
let mock =
Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v1/rooms/.*/hierarchy"));
self.mock_endpoint(mock, GetHierarchyEndpoint).expect_default_access_token()
}
pub fn mock_set_space_child(&self) -> MockEndpoint<'_, SetSpaceChildEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.space.child/.*?"));
self.mock_endpoint(mock, SetSpaceChildEndpoint).expect_default_access_token()
}
pub fn mock_set_space_parent(&self) -> MockEndpoint<'_, SetSpaceParentEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.space.parent"));
self.mock_endpoint(mock, SetSpaceParentEndpoint).expect_default_access_token()
}
pub fn mock_get_profile_field(
&self,
user_id: &UserId,
field: ProfileFieldName,
) -> MockEndpoint<'_, GetProfileFieldEndpoint> {
let mock = Mock::given(method("GET"))
.and(path(format!("/_matrix/client/v3/profile/{user_id}/{field}")));
self.mock_endpoint(mock, GetProfileFieldEndpoint { field })
}
pub fn mock_set_profile_field(
&self,
user_id: &UserId,
field: ProfileFieldName,
) -> MockEndpoint<'_, SetProfileFieldEndpoint> {
let mock = Mock::given(method("PUT"))
.and(path(format!("/_matrix/client/v3/profile/{user_id}/{field}")));
self.mock_endpoint(mock, SetProfileFieldEndpoint).expect_default_access_token()
}
pub fn mock_delete_profile_field(
&self,
user_id: &UserId,
field: ProfileFieldName,
) -> MockEndpoint<'_, DeleteProfileFieldEndpoint> {
let mock = Mock::given(method("DELETE"))
.and(path(format!("/_matrix/client/v3/profile/{user_id}/{field}")));
self.mock_endpoint(mock, DeleteProfileFieldEndpoint).expect_default_access_token()
}
pub fn mock_get_profile(&self, user_id: &UserId) -> MockEndpoint<'_, GetProfileEndpoint> {
let mock =
Mock::given(method("GET")).and(path(format!("/_matrix/client/v3/profile/{user_id}")));
self.mock_endpoint(mock, GetProfileEndpoint)
}
}
pub enum PushRuleIdSpec<'a> {
Some(&'a str),
Any,
}
impl<'a> PushRuleIdSpec<'a> {
pub fn to_path(&self) -> &str {
match self {
PushRuleIdSpec::Some(id) => id,
PushRuleIdSpec::Any => "[^/]*",
}
}
}
pub enum AnyRoomBuilder {
Invited(InvitedRoomBuilder),
Joined(JoinedRoomBuilder),
Left(LeftRoomBuilder),
Knocked(KnockedRoomBuilder),
}
impl AnyRoomBuilder {
fn room_id(&self) -> &RoomId {
match self {
AnyRoomBuilder::Invited(r) => r.room_id(),
AnyRoomBuilder::Joined(r) => r.room_id(),
AnyRoomBuilder::Left(r) => r.room_id(),
AnyRoomBuilder::Knocked(r) => r.room_id(),
}
}
}
impl From<InvitedRoomBuilder> for AnyRoomBuilder {
fn from(val: InvitedRoomBuilder) -> AnyRoomBuilder {
AnyRoomBuilder::Invited(val)
}
}
impl From<JoinedRoomBuilder> for AnyRoomBuilder {
fn from(val: JoinedRoomBuilder) -> AnyRoomBuilder {
AnyRoomBuilder::Joined(val)
}
}
impl From<LeftRoomBuilder> for AnyRoomBuilder {
fn from(val: LeftRoomBuilder) -> AnyRoomBuilder {
AnyRoomBuilder::Left(val)
}
}
impl From<KnockedRoomBuilder> for AnyRoomBuilder {
fn from(val: KnockedRoomBuilder) -> AnyRoomBuilder {
AnyRoomBuilder::Knocked(val)
}
}
const PATH_PERCENT_ENCODE_SET: &AsciiSet = &CONTROLS
.add(b' ')
.add(b'"')
.add(b'#')
.add(b'<')
.add(b'>')
.add(b'?')
.add(b'`')
.add(b'{')
.add(b'}')
.add(b'/');
fn percent_encoded_path(path: &str) -> String {
percent_encoding::utf8_percent_encode(path, PATH_PERCENT_ENCODE_SET).to_string()
}
pub struct MatrixMock<'a> {
pub(super) mock: Mock,
pub(super) server: &'a MockServer,
}
impl MatrixMock<'_> {
pub fn expect<T: Into<Times>>(self, num_calls: T) -> Self {
Self { mock: self.mock.expect(num_calls), ..self }
}
pub fn named(self, name: impl Into<String>) -> Self {
Self { mock: self.mock.named(name), ..self }
}
pub fn mock_once(self) -> Self {
Self { mock: self.mock.up_to_n_times(1).expect(1), ..self }
}
pub fn never(self) -> Self {
Self { mock: self.mock.expect(0), ..self }
}
pub fn up_to_n_times(self, num: u64) -> Self {
Self { mock: self.mock.up_to_n_times(num), ..self }
}
pub async fn mount(self) {
self.mock.mount(self.server).await;
}
pub async fn mount_as_scoped(self) -> MockGuard {
self.mock.mount_as_scoped(self.server).await
}
}
pub struct MockEndpoint<'a, T> {
server: &'a MockServer,
mock: MockBuilder,
endpoint: T,
expected_access_token: ExpectedAccessToken,
}
impl<'a, T> MockEndpoint<'a, T> {
fn new(server: &'a MockServer, mock: MockBuilder, endpoint: T) -> Self {
Self { server, mock, endpoint, expected_access_token: ExpectedAccessToken::Ignore }
}
pub fn expect_default_access_token(mut self) -> Self {
self.expected_access_token = ExpectedAccessToken::Default;
self
}
pub fn expect_access_token(mut self, access_token: &'static str) -> Self {
self.expected_access_token = ExpectedAccessToken::Custom(access_token);
self
}
pub fn expect_any_access_token(mut self) -> Self {
self.expected_access_token = ExpectedAccessToken::Any;
self
}
pub fn expect_missing_access_token(mut self) -> Self {
self.expected_access_token = ExpectedAccessToken::Missing;
self
}
pub fn ignore_access_token(mut self) -> Self {
self.expected_access_token = ExpectedAccessToken::Ignore;
self
}
pub fn respond_with<R: Respond + 'static>(self, func: R) -> MatrixMock<'a> {
let mock = self.mock.and(self.expected_access_token).respond_with(func);
MatrixMock { mock, server: self.server }
}
pub fn error500(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(500))
}
pub fn error_unrecognized(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404).set_body_json(json!({
"errcode": "M_UNRECOGNIZED",
"error": "Unrecognized request",
})))
}
pub fn error_unknown_token(self, soft_logout: bool) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"errcode": "M_UNKNOWN_TOKEN",
"error": "Unrecognized access token",
"soft_logout": soft_logout,
})))
}
fn ok_with_event_id(self, event_id: OwnedEventId) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })))
}
fn ok_empty_json(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
pub fn error_too_large(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(413).set_body_json(json!({
"errcode": "M_TOO_LARGE",
})))
}
}
enum ExpectedAccessToken {
Ignore,
Default,
Custom(&'static str),
Any,
Missing,
}
impl ExpectedAccessToken {
fn access_token(request: &Request) -> Option<&str> {
request
.headers
.get(&http::header::AUTHORIZATION)?
.to_str()
.ok()?
.strip_prefix("Bearer ")
.filter(|token| !token.is_empty())
}
}
impl wiremock::Match for ExpectedAccessToken {
fn matches(&self, request: &Request) -> bool {
match self {
Self::Ignore => true,
Self::Default => Self::access_token(request) == Some("1234"),
Self::Custom(token) => Self::access_token(request) == Some(token),
Self::Any => Self::access_token(request).is_some(),
Self::Missing => request.headers.get(&http::header::AUTHORIZATION).is_none(),
}
}
}
pub struct RoomSendEndpoint;
impl<'a> MockEndpoint<'a, RoomSendEndpoint> {
pub fn body_matches_partial_json(self, body: Value) -> Self {
Self { mock: self.mock.and(body_partial_json(body)), ..self }
}
pub fn for_type(self, event_type: MessageLikeEventType) -> Self {
Self {
mock: self
.mock
.and(path_regex(format!(r"^/_matrix/client/v3/rooms/.*/send/{event_type}",))),
..self
}
}
pub fn match_delayed_event(self, delay: Duration) -> Self {
Self {
mock: self
.mock
.and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
..self
}
}
pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
self.ok_with_event_id(returned_event_id.into())
}
pub fn ok_with_capture(
self,
returned_event_id: impl Into<OwnedEventId>,
event_sender: impl Into<OwnedUserId>,
) -> (Receiver<Raw<AnySyncTimelineEvent>>, MatrixMock<'a>) {
let event_id = returned_event_id.into();
let event_sender = event_sender.into();
let (sender, receiver) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(sender)));
let ret = self.respond_with(move |request: &Request| {
if let Some(sender) = sender.lock().unwrap().take() {
let uri = &request.url;
let path_segments = uri.path_segments();
let maybe_event_type = path_segments.and_then(|mut s| s.nth_back(1));
let event_type = maybe_event_type
.as_ref()
.map(|&e| e.to_owned())
.unwrap_or("m.room.message".to_owned());
let body: Value =
request.body_json().expect("The received body should be valid JSON");
let event = json!({
"event_id": event_id.clone(),
"sender": event_sender,
"type": event_type,
"origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
"content": body,
});
let event: Raw<AnySyncTimelineEvent> = from_value(event)
.expect("We should be able to create a raw event from the content");
sender.send(event).expect("We should be able to send the event to the receiver");
}
ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id.clone() }))
});
(receiver, ret)
}
}
#[derive(Default)]
pub struct RoomSendStateEndpoint {
state_key: Option<String>,
event_type: Option<StateEventType>,
}
impl<'a> MockEndpoint<'a, RoomSendStateEndpoint> {
fn generate_path_regexp(endpoint: &RoomSendStateEndpoint) -> String {
format!(
r"^/_matrix/client/v3/rooms/.*/state/{}/{}",
endpoint.event_type.as_ref().map_or_else(|| ".*".to_owned(), |t| t.to_string()),
endpoint.state_key.as_ref().map_or_else(|| ".*".to_owned(), |k| k.to_string())
)
}
pub fn body_matches_partial_json(self, body: Value) -> Self {
Self { mock: self.mock.and(body_partial_json(body)), ..self }
}
pub fn for_type(mut self, event_type: StateEventType) -> Self {
self.endpoint.event_type = Some(event_type);
Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
}
pub fn match_delayed_event(self, delay: Duration) -> Self {
Self {
mock: self
.mock
.and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
..self
}
}
pub fn for_key(mut self, state_key: String) -> Self {
self.endpoint.state_key = Some(state_key);
Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
}
pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
self.ok_with_event_id(returned_event_id.into())
}
}
pub struct SyncEndpoint {
sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
}
impl<'a> MockEndpoint<'a, SyncEndpoint> {
pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
if let Some(timeout) = timeout {
self.mock = self.mock.and(query_param("timeout", timeout.as_millis().to_string()));
} else {
self.mock = self.mock.and(query_param_is_missing("timeout"));
}
self
}
pub fn ok<F: FnOnce(&mut SyncResponseBuilder)>(self, func: F) -> MatrixMock<'a> {
let json_response = {
let mut builder = self.endpoint.sync_response_builder.lock().unwrap();
func(&mut builder);
builder.build_json_sync_response()
};
self.respond_with(ResponseTemplate::new(200).set_body_json(json_response))
}
pub async fn ok_and_run<F: FnOnce(&mut SyncResponseBuilder)>(self, client: &Client, func: F) {
let _scope = self.ok(func).mount_as_scoped().await;
let _response = client.sync_once(Default::default()).await.unwrap();
}
}
pub struct EncryptionStateEndpoint;
impl<'a> MockEndpoint<'a, EncryptionStateEndpoint> {
pub fn encrypted(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
)
}
#[cfg(feature = "experimental-encrypted-state-events")]
pub fn state_encrypted(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(
&*test_json::sync_events::ENCRYPTION_WITH_ENCRYPTED_STATE_EVENTS_CONTENT,
))
}
pub fn plain(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404).set_body_json(&*test_json::NOT_FOUND))
}
}
pub struct SetEncryptionStateEndpoint;
impl<'a> MockEndpoint<'a, SetEncryptionStateEndpoint> {
pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
self.ok_with_event_id(returned_event_id.into())
}
}
pub struct RoomRedactEndpoint;
impl<'a> MockEndpoint<'a, RoomRedactEndpoint> {
pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
self.ok_with_event_id(returned_event_id.into())
}
}
pub struct RoomEventEndpoint {
room: Option<OwnedRoomId>,
match_event_id: bool,
}
impl<'a> MockEndpoint<'a, RoomEventEndpoint> {
pub fn room(mut self, room: impl Into<OwnedRoomId>) -> Self {
self.endpoint.room = Some(room.into());
self
}
pub fn match_event_id(mut self) -> Self {
self.endpoint.match_event_id = true;
self
}
pub fn ok(self, event: TimelineEvent) -> MatrixMock<'a> {
let event_path = if self.endpoint.match_event_id {
let event_id = event.kind.event_id().expect("an event id is required");
event_id.as_str().replace("$", "\\$")
} else {
"".to_owned()
};
let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string());
let mock = self
.mock
.and(path_regex(format!(r"^/_matrix/client/v3/rooms/{room_path}/event/{event_path}")))
.respond_with(ResponseTemplate::new(200).set_body_json(event.into_raw().json()));
MatrixMock { server: self.server, mock }
}
}
pub struct RoomContextResponseTemplate {
event: TimelineEvent,
events_before: Vec<TimelineEvent>,
events_after: Vec<TimelineEvent>,
start: Option<String>,
end: Option<String>,
state_events: Vec<Raw<AnyStateEvent>>,
}
impl RoomContextResponseTemplate {
pub fn new(event: TimelineEvent) -> Self {
Self {
event,
events_before: Vec::new(),
events_after: Vec::new(),
start: None,
end: None,
state_events: Vec::new(),
}
}
pub fn events_before(mut self, events: Vec<TimelineEvent>) -> Self {
self.events_before = events;
self
}
pub fn events_after(mut self, events: Vec<TimelineEvent>) -> Self {
self.events_after = events;
self
}
pub fn start(mut self, start: impl Into<String>) -> Self {
self.start = Some(start.into());
self
}
pub fn end(mut self, end: impl Into<String>) -> Self {
self.end = Some(end.into());
self
}
pub fn state_events(mut self, state_events: Vec<Raw<AnyStateEvent>>) -> Self {
self.state_events = state_events;
self
}
}
pub struct RoomEventContextEndpoint {
room: Option<OwnedRoomId>,
match_event_id: bool,
}
impl<'a> MockEndpoint<'a, RoomEventContextEndpoint> {
pub fn room(mut self, room: impl Into<OwnedRoomId>) -> Self {
self.endpoint.room = Some(room.into());
self
}
pub fn match_event_id(mut self) -> Self {
self.endpoint.match_event_id = true;
self
}
pub fn ok(self, response: RoomContextResponseTemplate) -> MatrixMock<'a> {
let event_path = if self.endpoint.match_event_id {
let event_id = response.event.event_id().expect("an event id is required");
event_id.as_str().replace("$", "\\$")
} else {
"".to_owned()
};
let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string());
let mock = self
.mock
.and(path_regex(format!(r"^/_matrix/client/v3/rooms/{room_path}/context/{event_path}")))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"event": response.event.into_raw().json(),
"events_before": response.events_before.into_iter().map(|event| event.into_raw().json().to_owned()).collect::<Vec<_>>(),
"events_after": response.events_after.into_iter().map(|event| event.into_raw().json().to_owned()).collect::<Vec<_>>(),
"end": response.end,
"start": response.start,
"state": response.state_events,
})));
MatrixMock { server: self.server, mock }
}
}
pub struct RoomMessagesEndpoint;
impl<'a> MockEndpoint<'a, RoomMessagesEndpoint> {
pub fn match_limit(self, limit: u32) -> Self {
Self { mock: self.mock.and(query_param("limit", limit.to_string())), ..self }
}
pub fn match_from(self, from: &str) -> Self {
Self { mock: self.mock.and(query_param("from", from)), ..self }
}
pub fn ok(self, response: RoomMessagesResponseTemplate) -> MatrixMock<'a> {
let mut template = ResponseTemplate::new(200).set_body_json(json!({
"start": response.start,
"end": response.end,
"chunk": response.chunk,
"state": response.state,
}));
if let Some(delay) = response.delay {
template = template.set_delay(delay);
}
self.respond_with(template)
}
}
pub struct RoomMessagesResponseTemplate {
pub start: String,
pub end: Option<String>,
pub chunk: Vec<Raw<AnyTimelineEvent>>,
pub state: Vec<Raw<AnyStateEvent>>,
pub delay: Option<Duration>,
}
impl RoomMessagesResponseTemplate {
pub fn events(mut self, chunk: Vec<impl Into<Raw<AnyTimelineEvent>>>) -> Self {
self.chunk = chunk.into_iter().map(Into::into).collect();
self
}
pub fn end_token(mut self, token: impl Into<String>) -> Self {
self.end = Some(token.into());
self
}
pub fn with_delay(mut self, delay: Duration) -> Self {
self.delay = Some(delay);
self
}
}
impl Default for RoomMessagesResponseTemplate {
fn default() -> Self {
Self {
start: "start-token-unused".to_owned(),
end: Default::default(),
chunk: Default::default(),
state: Default::default(),
delay: None,
}
}
}
pub struct UploadEndpoint;
impl<'a> MockEndpoint<'a, UploadEndpoint> {
pub fn expect_mime_type(self, content_type: &str) -> Self {
Self { mock: self.mock.and(header("content-type", content_type)), ..self }
}
pub fn ok_with_capture(self, mxc_id: &MxcUri) -> (Receiver<Vec<u8>>, MatrixMock<'a>) {
let (sender, receiver) = oneshot::channel();
let sender = Arc::new(Mutex::new(Some(sender)));
let response_body = json!({"content_uri": mxc_id});
let ret = self.respond_with(move |request: &Request| {
let maybe_sender = sender.lock().unwrap().take();
if let Some(sender) = maybe_sender {
let body = request.body.clone();
let _ = sender.send(body);
}
ResponseTemplate::new(200).set_body_json(response_body.clone())
});
(receiver, ret)
}
pub fn ok(self, mxc_id: &MxcUri) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"content_uri": mxc_id
})))
}
}
pub struct ResolveRoomAliasEndpoint;
impl<'a> MockEndpoint<'a, ResolveRoomAliasEndpoint> {
pub fn for_alias(self, alias: impl Into<String>) -> Self {
let alias = alias.into();
Self {
mock: self.mock.and(path_regex(format!(
r"^/_matrix/client/v3/directory/room/{}",
percent_encoded_path(&alias)
))),
..self
}
}
pub fn ok(self, room_id: &str, servers: Vec<String>) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"room_id": room_id,
"servers": servers,
})))
}
pub fn not_found(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404).set_body_json(json!({
"errcode": "M_NOT_FOUND",
"error": "Room alias not found."
})))
}
}
pub struct CreateRoomAliasEndpoint;
impl<'a> MockEndpoint<'a, CreateRoomAliasEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct RemoveRoomAliasEndpoint;
impl<'a> MockEndpoint<'a, RemoveRoomAliasEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct PublicRoomsEndpoint;
impl<'a> MockEndpoint<'a, PublicRoomsEndpoint> {
pub fn ok(
self,
chunk: Vec<PublicRoomsChunk>,
next_batch: Option<String>,
prev_batch: Option<String>,
total_room_count_estimate: Option<u64>,
) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"chunk": chunk,
"next_batch": next_batch,
"prev_batch": prev_batch,
"total_room_count_estimate": total_room_count_estimate,
})))
}
pub fn ok_with_via_params(
self,
server_map: BTreeMap<OwnedServerName, Vec<PublicRoomsChunk>>,
) -> MatrixMock<'a> {
self.respond_with(move |req: &Request| {
#[derive(Deserialize)]
struct PartialRequest {
server: Option<OwnedServerName>,
}
let (_, server) = req
.url
.query_pairs()
.into_iter()
.find(|(key, _)| key == "server")
.expect("Server param not found in request URL");
let server = ServerName::parse(server).expect("Couldn't parse server name");
let chunk = server_map.get(&server).expect("Chunk for the server param not found");
ResponseTemplate::new(200).set_body_json(json!({
"chunk": chunk,
"total_room_count_estimate": chunk.len(),
}))
})
}
}
pub struct GetRoomVisibilityEndpoint;
impl<'a> MockEndpoint<'a, GetRoomVisibilityEndpoint> {
pub fn ok(self, visibility: Visibility) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"visibility": visibility,
})))
}
}
pub struct SetRoomVisibilityEndpoint;
impl<'a> MockEndpoint<'a, SetRoomVisibilityEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct RoomKeysVersionEndpoint;
impl<'a> MockEndpoint<'a, RoomKeysVersionEndpoint> {
pub fn exists(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key": "abcdefg",
"signatures": {},
},
"count": 42,
"etag": "anopaquestring",
"version": "1",
})))
}
pub fn none(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404).set_body_json(json!({
"errcode": "M_NOT_FOUND",
"error": "No current backup version"
})))
}
pub fn error429(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(429).set_body_json(json!({
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": 2000
})))
}
pub fn error404(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404))
}
}
pub struct AddRoomKeysVersionEndpoint;
impl<'a> MockEndpoint<'a, AddRoomKeysVersionEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"version": "1"
})))
.named("POST for the backup creation")
}
}
pub struct DeleteRoomKeysVersionEndpoint;
impl<'a> MockEndpoint<'a, DeleteRoomKeysVersionEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
.named("DELETE for the backup deletion")
}
}
pub struct SendToDeviceEndpoint;
impl<'a> MockEndpoint<'a, SendToDeviceEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct GetRoomMembersEndpoint;
impl<'a> MockEndpoint<'a, GetRoomMembersEndpoint> {
pub fn ok(self, members: Vec<Raw<RoomMemberEvent>>) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"chunk": members,
})))
}
}
pub struct InviteUserByIdEndpoint;
impl<'a> MockEndpoint<'a, InviteUserByIdEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct KickUserEndpoint;
impl<'a> MockEndpoint<'a, KickUserEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct BanUserEndpoint;
impl<'a> MockEndpoint<'a, BanUserEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct VersionsEndpoint;
impl<'a> MockEndpoint<'a, VersionsEndpoint> {
fn versions() -> Value {
json!([
"r0.0.1", "r0.2.0", "r0.3.0", "r0.4.0", "r0.5.0", "r0.6.0", "r0.6.1", "v1.1", "v1.2",
"v1.3", "v1.4", "v1.5", "v1.6", "v1.7", "v1.8", "v1.9", "v1.10", "v1.11"
])
}
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"unstable_features": {},
"versions": Self::versions()
})))
}
pub fn ok_with_unstable_features(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"unstable_features": {
"org.matrix.label_based_filtering": true,
"org.matrix.e2e_cross_signing": true,
"org.matrix.msc4028": true,
"org.matrix.simplified_msc3575": true,
},
"versions": Self::versions()
})))
}
pub fn ok_custom(
self,
versions: &[&str],
unstable_features: &BTreeMap<&str, bool>,
) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"unstable_features": unstable_features,
"versions": versions,
})))
}
}
pub struct RoomSummaryEndpoint;
impl<'a> MockEndpoint<'a, RoomSummaryEndpoint> {
pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"room_id": room_id,
"guest_can_join": true,
"num_joined_members": 1,
"world_readable": true,
"join_rule": "public",
})))
}
}
pub struct SetRoomPinnedEventsEndpoint;
impl<'a> MockEndpoint<'a, SetRoomPinnedEventsEndpoint> {
pub fn ok(self, event_id: OwnedEventId) -> MatrixMock<'a> {
self.ok_with_event_id(event_id)
}
pub fn unauthorized(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(400))
}
}
pub struct WhoAmIEndpoint;
impl<'a> MockEndpoint<'a, WhoAmIEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_with_device_id(device_id!("D3V1C31D"))
}
pub fn ok_with_device_id(self, device_id: &DeviceId) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"user_id": "@joe:example.org",
"device_id": device_id,
})))
}
}
pub struct UploadKeysEndpoint;
impl<'a> MockEndpoint<'a, UploadKeysEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"one_time_key_counts": {
"curve25519": 10,
"signed_curve25519": 20,
},
})))
}
}
pub struct QueryKeysEndpoint;
impl<'a> MockEndpoint<'a, QueryKeysEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct WellKnownEndpoint;
impl<'a> MockEndpoint<'a, WellKnownEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
let server_uri = self.server.uri();
self.ok_with_homeserver_url(&server_uri)
}
pub fn ok_with_homeserver_url(self, homeserver_url: &str) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"m.homeserver": {
"base_url": homeserver_url,
},
"m.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://livekit.example.com",
},
],
})))
}
pub fn error404(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(404))
}
}
pub struct UploadCrossSigningKeysEndpoint;
impl<'a> MockEndpoint<'a, UploadCrossSigningKeysEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
pub fn uiaa_invalid_password(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"errcode": "M_FORBIDDEN",
"error": "Invalid password",
"flows": [
{
"stages": [
"m.login.password"
]
}
],
"params": {},
"session": "oFIJVvtEOCKmRUTYKTYIIPHL"
})))
}
pub fn uiaa(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"flows": [
{
"stages": [
"m.login.password"
]
}
],
"params": {},
"session": "oFIJVvtEOCKmRUTYKTYIIPHL"
})))
}
pub fn uiaa_oauth(self) -> MatrixMock<'a> {
let server_uri = self.server.uri();
self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
"session": "dummy",
"flows": [{
"stages": [ "org.matrix.cross_signing_reset" ]
}],
"params": {
"org.matrix.cross_signing_reset": {
"url": format!("{server_uri}/account/?action=org.matrix.cross_signing_reset"),
}
},
"msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
})))
}
}
pub struct UploadCrossSigningSignaturesEndpoint;
impl<'a> MockEndpoint<'a, UploadCrossSigningSignaturesEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct RoomLeaveEndpoint;
impl<'a> MockEndpoint<'a, RoomLeaveEndpoint> {
pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"room_id": room_id,
})))
}
pub fn forbidden(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(403).set_body_json(json!({
"errcode": "M_FORBIDDEN",
"error": "sowwy",
})))
}
}
pub struct RoomForgetEndpoint;
impl<'a> MockEndpoint<'a, RoomForgetEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct LogoutEndpoint;
impl<'a> MockEndpoint<'a, LogoutEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct RoomThreadsEndpoint;
impl<'a> MockEndpoint<'a, RoomThreadsEndpoint> {
pub fn match_from(self, from: &str) -> Self {
Self { mock: self.mock.and(query_param("from", from)), ..self }
}
pub fn ok(
self,
chunk: Vec<Raw<AnyTimelineEvent>>,
next_batch: Option<String>,
) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"chunk": chunk,
"next_batch": next_batch
})))
}
}
#[derive(Default)]
pub struct RoomRelationsEndpoint {
event_id: Option<OwnedEventId>,
spec: Option<IncludeRelations>,
}
impl<'a> MockEndpoint<'a, RoomRelationsEndpoint> {
pub fn match_from(self, from: &str) -> Self {
Self { mock: self.mock.and(query_param("from", from)), ..self }
}
pub fn match_limit(self, limit: u32) -> Self {
Self { mock: self.mock.and(query_param("limit", limit.to_string())), ..self }
}
pub fn match_subrequest(mut self, spec: IncludeRelations) -> Self {
self.endpoint.spec = Some(spec);
self
}
pub fn match_target_event(mut self, event_id: OwnedEventId) -> Self {
self.endpoint.event_id = Some(event_id);
self
}
pub fn ok(mut self, response: RoomRelationsResponseTemplate) -> MatrixMock<'a> {
let event_spec = self
.endpoint
.event_id
.take()
.map(|event_id| event_id.as_str().replace("$", "\\$"))
.unwrap_or_else(|| ".*".to_owned());
match self.endpoint.spec.take() {
Some(IncludeRelations::RelationsOfType(rel_type)) => {
self.mock = self.mock.and(path_regex(format!(
r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}/{rel_type}$"
)));
}
Some(IncludeRelations::RelationsOfTypeAndEventType(rel_type, event_type)) => {
self.mock = self.mock.and(path_regex(format!(
r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}/{rel_type}/{event_type}$"
)));
}
_ => {
self.mock = self.mock.and(path_regex(format!(
r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}",
)));
}
}
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"chunk": response.chunk,
"next_batch": response.next_batch,
"prev_batch": response.prev_batch,
"recursion_depth": response.recursion_depth,
})))
}
}
fn global_account_data_mock_builder(
builder: MockBuilder,
user_id: &UserId,
event_type: GlobalAccountDataEventType,
) -> MockBuilder {
builder
.and(path_regex(format!(r"^/_matrix/client/v3/user/{user_id}/account_data/{event_type}",)))
}
#[cfg(feature = "experimental-element-recent-emojis")]
pub struct GetRecentEmojisEndpoint;
#[cfg(feature = "experimental-element-recent-emojis")]
impl<'a> MockEndpoint<'a, GetRecentEmojisEndpoint> {
pub fn ok(self, user_id: &UserId, emojis: Vec<(String, UInt)>) -> MatrixMock<'a> {
let mock =
global_account_data_mock_builder(self.mock, user_id, "io.element.recent_emoji".into())
.respond_with(
ResponseTemplate::new(200).set_body_json(json!({ "recent_emoji": emojis })),
);
MatrixMock { server: self.server, mock }
}
}
#[cfg(feature = "experimental-element-recent-emojis")]
pub struct UpdateRecentEmojisEndpoint {
pub(crate) request_body: Option<Vec<(String, UInt)>>,
}
#[cfg(feature = "experimental-element-recent-emojis")]
impl UpdateRecentEmojisEndpoint {
fn new() -> Self {
Self { request_body: None }
}
}
#[cfg(feature = "experimental-element-recent-emojis")]
impl<'a> MockEndpoint<'a, UpdateRecentEmojisEndpoint> {
pub fn match_emojis_in_request_body(self, emojis: Vec<(String, UInt)>) -> Self {
Self::new(
self.server,
self.mock.and(body_json(json!(RecentEmojisContent::new(emojis)))),
self.endpoint,
)
}
#[cfg(feature = "experimental-element-recent-emojis")]
pub fn ok(self, user_id: &UserId) -> MatrixMock<'a> {
let mock =
global_account_data_mock_builder(self.mock, user_id, "io.element.recent_emoji".into())
.respond_with(ResponseTemplate::new(200).set_body_json(()));
MatrixMock { server: self.server, mock }
}
}
#[cfg(feature = "e2e-encryption")]
pub struct GetDefaultSecretStorageKeyEndpoint;
#[cfg(feature = "e2e-encryption")]
impl<'a> MockEndpoint<'a, GetDefaultSecretStorageKeyEndpoint> {
pub fn ok(self, user_id: &UserId, key_id: &str) -> MatrixMock<'a> {
let mock = global_account_data_mock_builder(
self.mock,
user_id,
GlobalAccountDataEventType::SecretStorageDefaultKey,
)
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"key": key_id
})));
MatrixMock { server: self.server, mock }
}
}
#[cfg(feature = "e2e-encryption")]
pub struct GetSecretStorageKeyEndpoint;
#[cfg(feature = "e2e-encryption")]
impl<'a> MockEndpoint<'a, GetSecretStorageKeyEndpoint> {
pub fn ok(
self,
user_id: &UserId,
secret_storage_key_event_content: &ruma::events::secret_storage::key::SecretStorageKeyEventContent,
) -> MatrixMock<'a> {
let mock = global_account_data_mock_builder(
self.mock,
user_id,
GlobalAccountDataEventType::SecretStorageKey(
secret_storage_key_event_content.key_id.clone(),
),
)
.respond_with(ResponseTemplate::new(200).set_body_json(secret_storage_key_event_content));
MatrixMock { server: self.server, mock }
}
}
#[cfg(feature = "e2e-encryption")]
pub struct GetMasterSigningKeyEndpoint;
#[cfg(feature = "e2e-encryption")]
impl<'a> MockEndpoint<'a, GetMasterSigningKeyEndpoint> {
pub fn ok<B: Serialize>(self, user_id: &UserId, key_json: B) -> MatrixMock<'a> {
let mock = global_account_data_mock_builder(
self.mock,
user_id,
GlobalAccountDataEventType::from("m.cross_signing.master".to_owned()),
)
.respond_with(ResponseTemplate::new(200).set_body_json(key_json));
MatrixMock { server: self.server, mock }
}
}
#[derive(Default)]
pub struct RoomRelationsResponseTemplate {
pub chunk: Vec<Raw<AnyTimelineEvent>>,
pub next_batch: Option<String>,
pub prev_batch: Option<String>,
pub recursion_depth: Option<u32>,
}
impl RoomRelationsResponseTemplate {
pub fn events(mut self, chunk: Vec<impl Into<Raw<AnyTimelineEvent>>>) -> Self {
self.chunk = chunk.into_iter().map(Into::into).collect();
self
}
pub fn next_batch(mut self, token: impl Into<String>) -> Self {
self.next_batch = Some(token.into());
self
}
pub fn prev_batch(mut self, token: impl Into<String>) -> Self {
self.prev_batch = Some(token.into());
self
}
pub fn recursion_depth(mut self, depth: u32) -> Self {
self.recursion_depth = Some(depth);
self
}
}
pub struct ReceiptEndpoint;
impl<'a> MockEndpoint<'a, ReceiptEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
pub fn body_matches_partial_json(self, body: Value) -> Self {
Self { mock: self.mock.and(body_partial_json(body)), ..self }
}
pub fn body_json(self, body: Value) -> Self {
Self { mock: self.mock.and(body_json(body)), ..self }
}
pub fn match_thread(self, thread: ReceiptThread) -> Self {
if let Some(thread_str) = thread.as_str() {
self.body_matches_partial_json(json!({
"thread_id": thread_str
}))
} else {
self
}
}
pub fn match_event_id(self, event_id: &EventId) -> Self {
Self {
mock: self.mock.and(path_regex(format!(
r"^/_matrix/client/v3/rooms/.*/receipt/.*/{}$",
event_id.as_str().replace("$", "\\$")
))),
..self
}
}
}
pub struct ReadMarkersEndpoint;
impl<'a> MockEndpoint<'a, ReadMarkersEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct RoomAccountDataEndpoint;
impl<'a> MockEndpoint<'a, RoomAccountDataEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct AuthenticatedMediaConfigEndpoint;
impl<'a> MockEndpoint<'a, AuthenticatedMediaConfigEndpoint> {
pub fn ok(self, max_upload_size: UInt) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"m.upload.size": max_upload_size,
})))
}
pub fn ok_default(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"m.upload.size": UInt::MAX,
})))
}
}
pub struct MediaConfigEndpoint;
impl<'a> MockEndpoint<'a, MediaConfigEndpoint> {
pub fn ok(self, max_upload_size: UInt) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"m.upload.size": max_upload_size,
})))
}
}
pub struct LoginEndpoint;
impl<'a> MockEndpoint<'a, LoginEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN))
}
pub fn ok_with(self, response: LoginResponseTemplate200) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"access_token": response.access_token,
"device_id": response.device_id,
"user_id": response.user_id,
"expires_in": response.expires_in.map(|duration| { duration.as_millis() }),
"refresh_token": response.refresh_token,
"well_known": response.well_known.map(|vals| {
json!({
"m.homeserver": {
"base_url": vals.homeserver_url
},
"m.identity_server": vals.identity_url.map(|url| {
json!({
"base_url": url
})
})
})
}),
})))
}
pub fn body_matches_partial_json(self, body: Value) -> Self {
Self { mock: self.mock.and(body_partial_json(body)), ..self }
}
}
#[derive(Default)]
struct LoginResponseWellKnown {
homeserver_url: String,
identity_url: Option<String>,
}
#[derive(Default)]
pub struct LoginResponseTemplate200 {
access_token: Option<String>,
device_id: Option<OwnedDeviceId>,
expires_in: Option<Duration>,
refresh_token: Option<String>,
user_id: Option<OwnedUserId>,
well_known: Option<LoginResponseWellKnown>,
}
impl LoginResponseTemplate200 {
pub fn new<T1: Into<OwnedDeviceId>, T2: Into<OwnedUserId>>(
access_token: &str,
device_id: T1,
user_id: T2,
) -> Self {
Self {
access_token: Some(access_token.to_owned()),
device_id: Some(device_id.into()),
user_id: Some(user_id.into()),
..Default::default()
}
}
pub fn expires_in(mut self, value: Duration) -> Self {
self.expires_in = Some(value);
self
}
pub fn refresh_token(mut self, value: &str) -> Self {
self.refresh_token = Some(value.to_owned());
self
}
pub fn well_known(mut self, homeserver_url: String, identity_url: Option<String>) -> Self {
self.well_known = Some(LoginResponseWellKnown { homeserver_url, identity_url });
self
}
}
pub struct DevicesEndpoint;
impl<'a> MockEndpoint<'a, DevicesEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::DEVICES))
}
}
pub struct GetDeviceEndpoint;
impl<'a> MockEndpoint<'a, GetDeviceEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::DEVICE))
}
}
pub struct UserDirectoryEndpoint;
impl<'a> MockEndpoint<'a, UserDirectoryEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200)
.set_body_json(&*test_json::search_users::SEARCH_USERS_RESPONSE),
)
}
}
pub struct CreateRoomEndpoint;
impl<'a> MockEndpoint<'a, CreateRoomEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_json(json!({ "room_id": "!room:example.org"})),
)
}
}
pub struct UpgradeRoomEndpoint;
impl<'a> MockEndpoint<'a, UpgradeRoomEndpoint> {
pub fn ok_with(self, new_room_id: &RoomId) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200)
.set_body_json(json!({ "replacement_room": new_room_id.as_str()})),
)
}
}
pub struct MediaAllocateEndpoint;
impl<'a> MockEndpoint<'a, MediaAllocateEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
})))
}
}
pub struct MediaAllocatedUploadEndpoint;
impl<'a> MockEndpoint<'a, MediaAllocatedUploadEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
}
}
pub struct MediaDownloadEndpoint;
impl<'a> MockEndpoint<'a, MediaDownloadEndpoint> {
pub fn ok_plain_text(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_string("Hello, World!"))
}
pub fn ok_image(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_raw(b"binaryjpegfullimagedata", "image/jpeg"),
)
}
}
pub struct MediaThumbnailEndpoint;
impl<'a> MockEndpoint<'a, MediaThumbnailEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_raw(b"binaryjpegthumbnaildata", "image/jpeg"),
)
}
}
pub struct AuthedMediaDownloadEndpoint;
impl<'a> MockEndpoint<'a, AuthedMediaDownloadEndpoint> {
pub fn ok_plain_text(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_string("Hello, World!"))
}
pub fn ok_bytes(self, bytes: Vec<u8>) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_raw(bytes, "application/octet-stream"),
)
}
pub fn ok_image(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_raw(b"binaryjpegfullimagedata", "image/jpeg"),
)
}
}
pub struct AuthedMediaThumbnailEndpoint;
impl<'a> MockEndpoint<'a, AuthedMediaThumbnailEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(
ResponseTemplate::new(200).set_body_raw(b"binaryjpegthumbnaildata", "image/jpeg"),
)
}
}
pub struct JoinRoomEndpoint {
room_id: OwnedRoomId,
}
impl<'a> MockEndpoint<'a, JoinRoomEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
let room_id = self.endpoint.room_id.to_owned();
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"room_id": room_id,
})))
}
}
#[derive(Default)]
struct ThreadSubscriptionMatchers {
room_id: Option<OwnedRoomId>,
thread_root: Option<OwnedEventId>,
}
impl ThreadSubscriptionMatchers {
fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
self.room_id = Some(room_id);
self
}
fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
self.thread_root = Some(thread_root);
self
}
fn endpoint_regexp_uri(&self) -> String {
if self.room_id.is_some() || self.thread_root.is_some() {
format!(
"^/_matrix/client/unstable/io.element.msc4306/rooms/{}/thread/{}/subscription$",
self.room_id.as_deref().map(|s| s.as_str()).unwrap_or(".*"),
self.thread_root.as_deref().map(|s| s.as_str()).unwrap_or(".*").replace("$", "\\$")
)
} else {
"^/_matrix/client/unstable/io.element.msc4306/rooms/.*/thread/.*/subscription$"
.to_owned()
}
}
}
#[derive(Default)]
pub struct RoomGetThreadSubscriptionEndpoint {
matchers: ThreadSubscriptionMatchers,
}
impl<'a> MockEndpoint<'a, RoomGetThreadSubscriptionEndpoint> {
pub fn ok(mut self, automatic: bool) -> MatrixMock<'a> {
self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"automatic": automatic
})))
}
pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
self
}
pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
self
}
}
#[derive(Default)]
pub struct RoomPutThreadSubscriptionEndpoint {
matchers: ThreadSubscriptionMatchers,
}
impl<'a> MockEndpoint<'a, RoomPutThreadSubscriptionEndpoint> {
pub fn ok(mut self) -> MatrixMock<'a> {
self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
self.respond_with(ResponseTemplate::new(200))
}
pub fn conflicting_unsubscription(mut self) -> MatrixMock<'a> {
self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
self.respond_with(ResponseTemplate::new(409).set_body_json(json!({
"errcode": "IO.ELEMENT.MSC4306.M_CONFLICTING_UNSUBSCRIPTION",
"error": "the user unsubscribed after the subscription event id"
})))
}
pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
self
}
pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
self
}
pub fn match_automatic_event_id(mut self, up_to_event_id: &EventId) -> Self {
self.mock = self.mock.and(body_json(json!({
"automatic": up_to_event_id
})));
self
}
}
#[derive(Default)]
pub struct RoomDeleteThreadSubscriptionEndpoint {
matchers: ThreadSubscriptionMatchers,
}
impl<'a> MockEndpoint<'a, RoomDeleteThreadSubscriptionEndpoint> {
pub fn ok(mut self) -> MatrixMock<'a> {
self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
self.respond_with(ResponseTemplate::new(200))
}
pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
self
}
pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
self
}
}
pub struct EnablePushRuleEndpoint;
impl<'a> MockEndpoint<'a, EnablePushRuleEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct SetPushRulesActionsEndpoint;
impl<'a> MockEndpoint<'a, SetPushRulesActionsEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct SetPushRulesEndpoint;
impl<'a> MockEndpoint<'a, SetPushRulesEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct DeletePushRulesEndpoint;
impl<'a> MockEndpoint<'a, DeletePushRulesEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct FederationVersionEndpoint;
impl<'a> MockEndpoint<'a, FederationVersionEndpoint> {
pub fn ok(self, server_name: &str, version: &str) -> MatrixMock<'a> {
let response_body = json!({
"server": {
"name": server_name,
"version": version
}
});
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
}
pub fn ok_empty(self) -> MatrixMock<'a> {
let response_body = json!({});
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
}
}
#[derive(Default)]
pub struct GetThreadSubscriptionsEndpoint {
subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
delay: Option<Duration>,
}
impl<'a> MockEndpoint<'a, GetThreadSubscriptionsEndpoint> {
pub fn add_subscription(
mut self,
room_id: OwnedRoomId,
thread_root: OwnedEventId,
subscription: ThreadSubscription,
) -> Self {
self.endpoint.subscribed.entry(room_id).or_default().insert(thread_root, subscription);
self
}
pub fn add_unsubscription(
mut self,
room_id: OwnedRoomId,
thread_root: OwnedEventId,
unsubscription: ThreadUnsubscription,
) -> Self {
self.endpoint.unsubscribed.entry(room_id).or_default().insert(thread_root, unsubscription);
self
}
pub fn with_delay(mut self, delay: Duration) -> Self {
self.endpoint.delay = Some(delay);
self
}
pub fn match_from(self, from: &str) -> Self {
Self { mock: self.mock.and(query_param("from", from)), ..self }
}
pub fn match_to(self, to: &str) -> Self {
Self { mock: self.mock.and(query_param("to", to)), ..self }
}
pub fn ok(self, end: Option<String>) -> MatrixMock<'a> {
let response_body = json!({
"subscribed": self.endpoint.subscribed,
"unsubscribed": self.endpoint.unsubscribed,
"end": end,
});
let mut template = ResponseTemplate::new(200).set_body_json(response_body);
if let Some(delay) = self.endpoint.delay {
template = template.set_delay(delay);
}
self.respond_with(template)
}
}
#[derive(Default)]
pub struct GetHierarchyEndpoint;
impl<'a> MockEndpoint<'a, GetHierarchyEndpoint> {
pub fn ok_with_room_ids(self, room_ids: Vec<&RoomId>) -> MatrixMock<'a> {
let rooms = room_ids
.iter()
.map(|id| {
json!({
"room_id": id,
"num_joined_members": 1,
"world_readable": false,
"guest_can_join": false,
"children_state": []
})
})
.collect::<Vec<_>>();
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rooms": rooms,
})))
}
pub fn ok_with_room_ids_and_children_state(
self,
room_ids: Vec<&RoomId>,
children_state: Vec<(&RoomId, Vec<&ServerName>)>,
) -> MatrixMock<'a> {
let children_state = children_state
.into_iter()
.map(|(id, via)| {
json!({
"type":
"m.space.child",
"state_key": id,
"content": { "via": via },
"sender": "@bob:matrix.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now()
})
})
.collect::<Vec<_>>();
let rooms = room_ids
.iter()
.map(|id| {
json!({
"room_id": id,
"num_joined_members": 1,
"world_readable": false,
"guest_can_join": false,
"children_state": children_state
})
})
.collect::<Vec<_>>();
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rooms": rooms,
})))
}
pub fn ok(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rooms": []
})))
}
}
pub struct SetSpaceChildEndpoint;
impl<'a> MockEndpoint<'a, SetSpaceChildEndpoint> {
pub fn ok(self, event_id: OwnedEventId) -> MatrixMock<'a> {
self.ok_with_event_id(event_id)
}
pub fn unauthorized(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(400))
}
}
pub struct SetSpaceParentEndpoint;
impl<'a> MockEndpoint<'a, SetSpaceParentEndpoint> {
pub fn ok(self, event_id: OwnedEventId) -> MatrixMock<'a> {
self.ok_with_event_id(event_id)
}
pub fn unauthorized(self) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(400))
}
}
pub struct SlidingSyncEndpoint;
impl<'a> MockEndpoint<'a, SlidingSyncEndpoint> {
pub fn ok(self, response: v5::Response) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"txn_id": response.txn_id,
"pos": response.pos,
"lists": response.lists,
"rooms": response.rooms,
"extensions": response.extensions,
})))
}
pub async fn ok_and_run<F: FnOnce(SlidingSyncBuilder) -> SlidingSyncBuilder>(
self,
client: &Client,
on_builder: F,
response: v5::Response,
) {
let _scope = self.ok(response).mount_as_scoped().await;
let sliding_sync =
on_builder(client.sliding_sync("test_id").unwrap()).build().await.unwrap();
let _summary = sliding_sync.sync_once().await.unwrap();
}
}
pub struct GetProfileFieldEndpoint {
field: ProfileFieldName,
}
impl<'a> MockEndpoint<'a, GetProfileFieldEndpoint> {
pub fn ok_with_value(self, value: Option<Value>) -> MatrixMock<'a> {
if let Some(value) = value {
let field = self.endpoint.field.to_string();
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
field: value,
})))
} else {
self.ok_empty_json()
}
}
}
pub struct SetProfileFieldEndpoint;
impl<'a> MockEndpoint<'a, SetProfileFieldEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct DeleteProfileFieldEndpoint;
impl<'a> MockEndpoint<'a, DeleteProfileFieldEndpoint> {
pub fn ok(self) -> MatrixMock<'a> {
self.ok_empty_json()
}
}
pub struct GetProfileEndpoint;
impl<'a> MockEndpoint<'a, GetProfileEndpoint> {
pub fn ok_with_fields(self, fields: Vec<ProfileFieldValue>) -> MatrixMock<'a> {
let profile = fields
.iter()
.map(|field| (field.field_name(), field.value()))
.collect::<BTreeMap<_, _>>();
self.respond_with(ResponseTemplate::new(200).set_body_json(profile))
}
}