matrix_sdk/test_utils/mocks/mod.rs
1// Copyright 2024 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Helpers to mock a server and have a client automatically connected to that
16//! server, for the purpose of integration tests.
17
18#![allow(missing_debug_implementations)]
19
20use std::{
21 collections::BTreeMap,
22 sync::{Arc, Mutex, atomic::AtomicU32},
23};
24
25use js_int::UInt;
26use matrix_sdk_base::deserialized_responses::TimelineEvent;
27#[cfg(feature = "experimental-element-recent-emojis")]
28use matrix_sdk_base::recent_emojis::RecentEmojisContent;
29use matrix_sdk_test::{
30 InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder,
31 SyncResponseBuilder, test_json,
32};
33use percent_encoding::{AsciiSet, CONTROLS};
34use ruma::{
35 DeviceId, EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId,
36 OwnedOneTimeKeyId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
37 api::client::{
38 receipt::create_receipt::v3::ReceiptType,
39 room::Visibility,
40 sync::sync_events::v5,
41 threads::get_thread_subscriptions_changes::unstable::{
42 ThreadSubscription, ThreadUnsubscription,
43 },
44 },
45 device_id,
46 directory::PublicRoomsChunk,
47 encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
48 events::{
49 AnyStateEvent, AnySyncTimelineEvent, AnyTimelineEvent, GlobalAccountDataEventType,
50 MessageLikeEventType, RoomAccountDataEventType, StateEventType, receipt::ReceiptThread,
51 room::member::RoomMemberEvent,
52 },
53 media::Method,
54 push::RuleKind,
55 serde::Raw,
56 time::Duration,
57};
58use serde::Deserialize;
59use serde_json::{Value, from_value, json};
60use tokio::sync::oneshot::{self, Receiver};
61use wiremock::{
62 Mock, MockBuilder, MockGuard, MockServer, Request, Respond, ResponseTemplate, Times,
63 matchers::{
64 body_json, body_partial_json, header, method, path, path_regex, query_param,
65 query_param_is_missing,
66 },
67};
68
69#[cfg(feature = "e2e-encryption")]
70pub mod encryption;
71pub mod oauth;
72
73use super::client::MockClientBuilder;
74use crate::{Client, OwnedServerName, Room, SlidingSyncBuilder, room::IncludeRelations};
75
76/// Structure used to store the crypto keys uploaded to the server.
77/// They will be served back to clients when requested.
78#[derive(Debug, Default)]
79struct Keys {
80 device: BTreeMap<OwnedUserId, BTreeMap<String, Raw<DeviceKeys>>>,
81 master: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
82 self_signing: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
83 user_signing: BTreeMap<OwnedUserId, Raw<CrossSigningKey>>,
84 one_time_keys: BTreeMap<
85 OwnedUserId,
86 BTreeMap<OwnedDeviceId, BTreeMap<OwnedOneTimeKeyId, Raw<OneTimeKey>>>,
87 >,
88}
89
90/// A [`wiremock`] [`MockServer`] along with useful methods to help mocking
91/// Matrix client-server API endpoints easily.
92///
93/// It implements mock endpoints, limiting the shared code as much as possible,
94/// so the mocks are still flexible to use as scoped/unscoped mounts, named, and
95/// so on.
96///
97/// It works like this:
98///
99/// * start by saying which endpoint you'd like to mock, e.g.
100/// [`Self::mock_room_send()`]. This returns a specialized [`MockEndpoint`]
101/// data structure, with its own impl. For this example, it's
102/// `MockEndpoint<RoomSendEndpoint>`.
103/// * configure the response on the endpoint-specific mock data structure. For
104/// instance, if you want the sending to result in a transient failure, call
105/// [`MockEndpoint::error500`]; if you want it to succeed and return the event
106/// `$42`, call [`MockEndpoint::ok()`]. It's still possible to call
107/// [`MockEndpoint::respond_with()`], as we do with wiremock MockBuilder, for
108/// maximum flexibility when the helpers aren't sufficient.
109/// * once the endpoint's response is configured, for any mock builder, you get
110/// a [`MatrixMock`]; this is a plain [`wiremock::Mock`] with the server
111/// curried, so one doesn't have to pass it around when calling
112/// [`MatrixMock::mount()`] or [`MatrixMock::mount_as_scoped()`]. As such, it
113/// mostly defers its implementations to [`wiremock::Mock`] under the hood.
114///
115/// # Examples
116///
117/// ```
118/// # tokio_test::block_on(async {
119/// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
120/// use serde_json::json;
121///
122/// // First create the mock server and client pair.
123/// let mock_server = MatrixMockServer::new().await;
124/// let client = mock_server.client_builder().build().await;
125///
126/// // Let's say that our rooms are not encrypted.
127/// mock_server.mock_room_state_encryption().plain().mount().await;
128///
129/// // Let us get a room where we will send an event.
130/// let room = mock_server
131/// .sync_joined_room(&client, room_id!("!room_id:localhost"))
132/// .await;
133///
134/// // Now we mock the endpoint so we can actually send the event.
135/// let event_id = event_id!("$some_id");
136/// let send_guard = mock_server
137/// .mock_room_send()
138/// .ok(event_id)
139/// .expect(1)
140/// .mount_as_scoped()
141/// .await;
142///
143/// // And we send it out.
144/// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
145///
146/// assert_eq!(
147/// event_id,
148/// response.event_id,
149/// "The event ID we mocked should match the one we received when we sent the event"
150/// );
151/// # anyhow::Ok(()) });
152/// ```
153pub struct MatrixMockServer {
154 server: MockServer,
155
156 /// Make the sync response builder stateful, to keep in memory the batch
157 /// token and avoid the client ignoring subsequent responses after the first
158 /// one.
159 sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
160
161 /// Make this mock server capable of mocking real end to end communications
162 keys: Arc<Mutex<Keys>>,
163
164 /// For crypto API end-points to work we need to be able to recognise
165 /// what client is doing the request by mapping the token to the user_id
166 token_to_user_id_map: Arc<Mutex<BTreeMap<String, OwnedUserId>>>,
167 token_counter: AtomicU32,
168}
169
170impl MatrixMockServer {
171 /// Create a new [`wiremock`] server specialized for Matrix usage.
172 pub async fn new() -> Self {
173 let server = MockServer::start().await;
174 let keys: Arc<Mutex<Keys>> = Default::default();
175 Self {
176 server,
177 sync_response_builder: Default::default(),
178 keys,
179 token_to_user_id_map: Default::default(),
180 token_counter: AtomicU32::new(0),
181 }
182 }
183
184 /// Creates a new [`MatrixMockServer`] from a [`wiremock`] server.
185 pub fn from_server(server: MockServer) -> Self {
186 let keys: Arc<Mutex<Keys>> = Default::default();
187 Self {
188 server,
189 sync_response_builder: Default::default(),
190 keys,
191 token_to_user_id_map: Default::default(),
192 token_counter: AtomicU32::new(0),
193 }
194 }
195
196 /// Creates a new [`MockClientBuilder`] configured to use this server,
197 /// preconfigured with a session expected by the server endpoints.
198 pub fn client_builder(&self) -> MockClientBuilder {
199 MockClientBuilder::new(Some(&self.server.uri()))
200 }
201
202 /// Return the underlying [`wiremock`] server.
203 pub fn server(&self) -> &MockServer {
204 &self.server
205 }
206
207 /// Return the URI of this server.
208 pub fn uri(&self) -> String {
209 self.server.uri()
210 }
211
212 /// Get an `OAuthMockServer` that uses the same mock server as this one.
213 pub fn oauth(&self) -> oauth::OAuthMockServer<'_> {
214 oauth::OAuthMockServer::new(self)
215 }
216
217 /// Mock the given endpoint.
218 fn mock_endpoint<T>(&self, mock: MockBuilder, endpoint: T) -> MockEndpoint<'_, T> {
219 MockEndpoint::new(&self.server, mock, endpoint)
220 }
221
222 /// Overrides the sync/ endpoint with knowledge that the given
223 /// invited/joined/knocked/left room exists, runs a sync and returns the
224 /// given room.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// # tokio_test::block_on(async {
230 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
231 /// use matrix_sdk_test::LeftRoomBuilder;
232 ///
233 /// let mock_server = MatrixMockServer::new().await;
234 /// let client = mock_server.client_builder().build().await;
235 ///
236 /// let left_room = mock_server
237 /// .sync_room(&client, LeftRoomBuilder::new(room_id!("!room_id:localhost")))
238 /// .await;
239 /// # anyhow::Ok(()) });
240 pub async fn sync_room(&self, client: &Client, room_data: impl Into<AnyRoomBuilder>) -> Room {
241 let any_room = room_data.into();
242 let room_id = any_room.room_id().to_owned();
243
244 self.mock_sync()
245 .ok_and_run(client, move |builder| match any_room {
246 AnyRoomBuilder::Invited(invited) => {
247 builder.add_invited_room(invited);
248 }
249 AnyRoomBuilder::Joined(joined) => {
250 builder.add_joined_room(joined);
251 }
252 AnyRoomBuilder::Left(left) => {
253 builder.add_left_room(left);
254 }
255 AnyRoomBuilder::Knocked(knocked) => {
256 builder.add_knocked_room(knocked);
257 }
258 })
259 .await;
260
261 client.get_room(&room_id).expect("look at me, the room is known now")
262 }
263
264 /// Overrides the sync/ endpoint with knowledge that the given room exists
265 /// in the joined state, runs a sync and returns the given room.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// # tokio_test::block_on(async {
271 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
272 ///
273 /// let mock_server = MatrixMockServer::new().await;
274 /// let client = mock_server.client_builder().build().await;
275 ///
276 /// let room = mock_server
277 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
278 /// .await;
279 /// # anyhow::Ok(()) });
280 pub async fn sync_joined_room(&self, client: &Client, room_id: &RoomId) -> Room {
281 self.sync_room(client, JoinedRoomBuilder::new(room_id)).await
282 }
283
284 /// Verify that the previous mocks expected number of requests match
285 /// reality, and then cancels all active mocks.
286 ///
287 /// # Examples
288 ///
289 /// ```
290 /// # tokio_test::block_on(async {
291 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
292 /// use serde_json::json;
293 ///
294 /// let mock_server = MatrixMockServer::new().await;
295 /// let client = mock_server.client_builder().build().await;
296 ///
297 /// mock_server.mock_room_state_encryption().plain().mount().await;
298 /// let room = mock_server
299 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
300 /// .await;
301 /// mock_server.mock_room_send().ok(event_id!("$some_id")).mount().await;
302 ///
303 /// // This will succeed.
304 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
305 ///
306 /// // Now we reset the mocks.
307 /// mock_server.verify_and_reset().await;
308 ///
309 /// // And we can't send anymore.
310 /// let response = room
311 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
312 /// .await
313 /// .expect_err("We removed the mock so sending should now fail");
314 /// # anyhow::Ok(()) });
315 /// ```
316 pub async fn verify_and_reset(&self) {
317 self.server.verify().await;
318 self.server.reset().await;
319 }
320}
321
322// Specific mount endpoints.
323impl MatrixMockServer {
324 /// Mocks a sync endpoint.
325 ///
326 /// # Examples
327 ///
328 /// ```
329 /// # tokio_test::block_on(async {
330 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
331 /// use matrix_sdk_test::JoinedRoomBuilder;
332 ///
333 /// // First create the mock server and client pair.
334 /// let mock_server = MatrixMockServer::new().await;
335 /// let client = mock_server.client_builder().build().await;
336 /// let room_id = room_id!("!room_id:localhost");
337 ///
338 /// // Let's emulate what `MatrixMockServer::sync_joined_room()` does.
339 /// mock_server
340 /// .mock_sync()
341 /// .ok_and_run(&client, |builder| {
342 /// builder.add_joined_room(JoinedRoomBuilder::new(room_id));
343 /// })
344 /// .await;
345 ///
346 /// let room = client
347 /// .get_room(room_id)
348 /// .expect("The room should be available after we mocked the sync");
349 /// # anyhow::Ok(()) });
350 /// ```
351 pub fn mock_sync(&self) -> MockEndpoint<'_, SyncEndpoint> {
352 let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/sync"));
353 self.mock_endpoint(
354 mock,
355 SyncEndpoint { sync_response_builder: self.sync_response_builder.clone() },
356 )
357 }
358
359 /// Mocks the sliding sync endpoint.
360 pub fn mock_sliding_sync(&self) -> MockEndpoint<'_, SlidingSyncEndpoint> {
361 let mock = Mock::given(method("POST"))
362 .and(path("/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"));
363 self.mock_endpoint(mock, SlidingSyncEndpoint)
364 }
365
366 /// Creates a prebuilt mock for joining a room.
367 ///
368 /// # Examples
369 ///
370 /// ```
371 /// # tokio_test::block_on(async {
372 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
373 /// use serde_json::json;
374 ///
375 /// let mock_server = MatrixMockServer::new().await;
376 /// let client = mock_server.client_builder().build().await;
377 /// let room_id = room_id!("!test:localhost");
378 ///
379 /// mock_server.mock_room_join(room_id).ok().mount();
380 ///
381 /// let room = client.join_room_by_id(room_id).await?;
382 ///
383 /// assert_eq!(
384 /// room_id,
385 /// room.room_id(),
386 /// "The room ID we mocked should match the one we received when we joined the room"
387 /// );
388 /// # anyhow::Ok(()) });
389 /// ```
390 pub fn mock_room_join(&self, room_id: &RoomId) -> MockEndpoint<'_, JoinRoomEndpoint> {
391 let mock = Mock::given(method("POST"))
392 .and(path_regex(format!("^/_matrix/client/v3/rooms/{room_id}/join")));
393 self.mock_endpoint(mock, JoinRoomEndpoint { room_id: room_id.to_owned() })
394 }
395
396 /// Creates a prebuilt mock for sending an event in a room.
397 ///
398 /// Note: works with *any* room.
399 ///
400 /// # Examples
401 ///
402 /// ```
403 /// # tokio_test::block_on(async {
404 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
405 /// use serde_json::json;
406 ///
407 /// let mock_server = MatrixMockServer::new().await;
408 /// let client = mock_server.client_builder().build().await;
409 ///
410 /// mock_server.mock_room_state_encryption().plain().mount().await;
411 ///
412 /// let room = mock_server
413 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
414 /// .await;
415 ///
416 /// let event_id = event_id!("$some_id");
417 /// mock_server
418 /// .mock_room_send()
419 /// .ok(event_id)
420 /// .expect(1)
421 /// .mount()
422 /// .await;
423 ///
424 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
425 ///
426 /// assert_eq!(
427 /// event_id,
428 /// response.event_id,
429 /// "The event ID we mocked should match the one we received when we sent the event"
430 /// );
431 /// # anyhow::Ok(()) });
432 /// ```
433 pub fn mock_room_send(&self) -> MockEndpoint<'_, RoomSendEndpoint> {
434 let mock = Mock::given(method("PUT"))
435 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/send/.*".to_owned()));
436 self.mock_endpoint(mock, RoomSendEndpoint)
437 }
438
439 /// Creates a prebuilt mock for sending a state event in a room.
440 ///
441 /// Similar to: [`MatrixMockServer::mock_room_send`]
442 ///
443 /// Note: works with *any* room.
444 /// Note: works with *any* event type.
445 ///
446 /// ```
447 /// # tokio_test::block_on(async {
448 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
449 /// use serde_json::json;
450 ///
451 /// let mock_server = MatrixMockServer::new().await;
452 /// let client = mock_server.client_builder().build().await;
453 ///
454 /// mock_server.mock_room_state_encryption().plain().mount().await;
455 ///
456 /// let room = mock_server
457 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
458 /// .await;
459 ///
460 /// let event_id = event_id!("$some_id");
461 /// mock_server
462 /// .mock_room_send_state()
463 /// .ok(event_id)
464 /// .expect(1)
465 /// .mount()
466 /// .await;
467 ///
468 /// let response_not_mocked = room.send_raw("m.room.create", json!({ "body": "Hello world" })).await;
469 /// // The `/send` endpoint should not be mocked by the server.
470 /// assert!(response_not_mocked.is_err());
471 ///
472 ///
473 /// let response = room.send_state_event_raw("m.room.message", "my_key", json!({ "body": "Hello world" })).await?;
474 /// // The `/state` endpoint should be mocked by the server.
475 /// assert_eq!(
476 /// event_id,
477 /// response.event_id,
478 /// "The event ID we mocked should match the one we received when we sent the event"
479 /// );
480 /// # anyhow::Ok(()) });
481 /// ```
482 pub fn mock_room_send_state(&self) -> MockEndpoint<'_, RoomSendStateEndpoint> {
483 let mock =
484 Mock::given(method("PUT")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/.*/.*"));
485 self.mock_endpoint(mock, RoomSendStateEndpoint::default()).expect_default_access_token()
486 }
487
488 /// Creates a prebuilt mock for asking whether *a* room is encrypted or not.
489 ///
490 /// Note: Applies to all rooms.
491 ///
492 /// # Examples
493 ///
494 /// ```
495 /// # tokio_test::block_on(async {
496 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
497 ///
498 /// let mock_server = MatrixMockServer::new().await;
499 /// let client = mock_server.client_builder().build().await;
500 ///
501 /// mock_server.mock_room_state_encryption().encrypted().mount().await;
502 ///
503 /// let room = mock_server
504 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
505 /// .await;
506 ///
507 /// assert!(
508 /// room.latest_encryption_state().await?.is_encrypted(),
509 /// "The room should be marked as encrypted."
510 /// );
511 /// # anyhow::Ok(()) });
512 /// ```
513 pub fn mock_room_state_encryption(&self) -> MockEndpoint<'_, EncryptionStateEndpoint> {
514 let mock = Mock::given(method("GET"))
515 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
516 self.mock_endpoint(mock, EncryptionStateEndpoint).expect_default_access_token()
517 }
518
519 /// Creates a prebuilt mock for setting the room encryption state.
520 ///
521 /// Note: Applies to all rooms.
522 ///
523 /// # Examples
524 ///
525 /// ```
526 /// # tokio_test::block_on(async {
527 /// use matrix_sdk::{
528 /// ruma::{event_id, room_id},
529 /// test_utils::mocks::MatrixMockServer,
530 /// };
531 ///
532 /// let mock_server = MatrixMockServer::new().await;
533 /// let client = mock_server.client_builder().build().await;
534 ///
535 /// mock_server.mock_room_state_encryption().plain().mount().await;
536 /// mock_server
537 /// .mock_set_room_state_encryption()
538 /// .ok(event_id!("$id"))
539 /// .mock_once()
540 /// .mount()
541 /// .await;
542 ///
543 /// let room = mock_server
544 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
545 /// .await;
546 ///
547 /// room.enable_encryption()
548 /// .await
549 /// .expect("We should be able to enable encryption in the room");
550 /// # anyhow::Ok(()) });
551 /// ```
552 pub fn mock_set_room_state_encryption(&self) -> MockEndpoint<'_, SetEncryptionStateEndpoint> {
553 let mock = Mock::given(method("PUT"))
554 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
555 self.mock_endpoint(mock, SetEncryptionStateEndpoint).expect_default_access_token()
556 }
557
558 /// Creates a prebuilt mock for the room redact endpoint.
559 ///
560 /// # Examples
561 ///
562 /// ```
563 /// # tokio_test::block_on(async {
564 /// use matrix_sdk::{
565 /// ruma::{event_id, room_id},
566 /// test_utils::mocks::MatrixMockServer,
567 /// };
568 ///
569 /// let mock_server = MatrixMockServer::new().await;
570 /// let client = mock_server.client_builder().build().await;
571 /// let event_id = event_id!("$id");
572 ///
573 /// mock_server.mock_room_redact().ok(event_id).mock_once().mount().await;
574 ///
575 /// let room = mock_server
576 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
577 /// .await;
578 ///
579 /// room.redact(event_id, None, None)
580 /// .await
581 /// .expect("We should be able to redact events in the room");
582 /// # anyhow::Ok(()) });
583 /// ```
584 pub fn mock_room_redact(&self) -> MockEndpoint<'_, RoomRedactEndpoint> {
585 let mock = Mock::given(method("PUT"))
586 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/redact/.*?/.*?"));
587 self.mock_endpoint(mock, RoomRedactEndpoint).expect_default_access_token()
588 }
589
590 /// Creates a prebuilt mock for retrieving an event with /room/.../event.
591 pub fn mock_room_event(&self) -> MockEndpoint<'_, RoomEventEndpoint> {
592 let mock = Mock::given(method("GET"));
593 self.mock_endpoint(mock, RoomEventEndpoint { room: None, match_event_id: false })
594 .expect_default_access_token()
595 }
596
597 /// Creates a prebuilt mock for retrieving an event with /room/.../context.
598 pub fn mock_room_event_context(&self) -> MockEndpoint<'_, RoomEventContextEndpoint> {
599 let mock = Mock::given(method("GET"));
600 self.mock_endpoint(mock, RoomEventContextEndpoint { room: None, match_event_id: false })
601 .expect_default_access_token()
602 }
603
604 /// Create a prebuild mock for paginating room message with the `/messages`
605 /// endpoint.
606 pub fn mock_room_messages(&self) -> MockEndpoint<'_, RoomMessagesEndpoint> {
607 let mock =
608 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/messages$"));
609 self.mock_endpoint(mock, RoomMessagesEndpoint).expect_default_access_token()
610 }
611
612 /// Create a prebuilt mock for uploading media.
613 pub fn mock_upload(&self) -> MockEndpoint<'_, UploadEndpoint> {
614 let mock = Mock::given(method("POST")).and(path("/_matrix/media/v3/upload"));
615 self.mock_endpoint(mock, UploadEndpoint)
616 }
617
618 /// Create a prebuilt mock for resolving room aliases.
619 ///
620 /// # Examples
621 ///
622 /// ```
623 /// # tokio_test::block_on(async {
624 /// use matrix_sdk::{
625 /// ruma::{owned_room_id, room_alias_id},
626 /// test_utils::mocks::MatrixMockServer,
627 /// };
628 /// let mock_server = MatrixMockServer::new().await;
629 /// let client = mock_server.client_builder().build().await;
630 ///
631 /// mock_server
632 /// .mock_room_directory_resolve_alias()
633 /// .ok("!a:b.c", Vec::new())
634 /// .mock_once()
635 /// .mount()
636 /// .await;
637 ///
638 /// let res = client
639 /// .resolve_room_alias(room_alias_id!("#a:b.c"))
640 /// .await
641 /// .expect("We should be able to resolve the room alias");
642 /// assert_eq!(res.room_id, owned_room_id!("!a:b.c"));
643 /// # anyhow::Ok(()) });
644 /// ```
645 pub fn mock_room_directory_resolve_alias(&self) -> MockEndpoint<'_, ResolveRoomAliasEndpoint> {
646 let mock =
647 Mock::given(method("GET")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
648 self.mock_endpoint(mock, ResolveRoomAliasEndpoint)
649 }
650
651 /// Create a prebuilt mock for publishing room aliases in the room
652 /// directory.
653 ///
654 /// # Examples
655 ///
656 /// ```
657 /// # tokio_test::block_on(async {
658 /// use matrix_sdk::{
659 /// ruma::{room_alias_id, room_id},
660 /// test_utils::mocks::MatrixMockServer,
661 /// };
662 ///
663 /// let mock_server = MatrixMockServer::new().await;
664 /// let client = mock_server.client_builder().build().await;
665 ///
666 /// mock_server
667 /// .mock_room_directory_create_room_alias()
668 /// .ok()
669 /// .mock_once()
670 /// .mount()
671 /// .await;
672 ///
673 /// client
674 /// .create_room_alias(room_alias_id!("#a:b.c"), room_id!("!a:b.c"))
675 /// .await
676 /// .expect("We should be able to create a room alias");
677 /// # anyhow::Ok(()) });
678 /// ```
679 pub fn mock_room_directory_create_room_alias(
680 &self,
681 ) -> MockEndpoint<'_, CreateRoomAliasEndpoint> {
682 let mock =
683 Mock::given(method("PUT")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
684 self.mock_endpoint(mock, CreateRoomAliasEndpoint)
685 }
686
687 /// Create a prebuilt mock for removing room aliases from the room
688 /// directory.
689 ///
690 /// # Examples
691 ///
692 /// ```
693 /// # tokio_test::block_on(async {
694 /// use matrix_sdk::{
695 /// ruma::room_alias_id, test_utils::mocks::MatrixMockServer,
696 /// };
697 ///
698 /// let mock_server = MatrixMockServer::new().await;
699 /// let client = mock_server.client_builder().build().await;
700 ///
701 /// mock_server
702 /// .mock_room_directory_remove_room_alias()
703 /// .ok()
704 /// .mock_once()
705 /// .mount()
706 /// .await;
707 ///
708 /// client
709 /// .remove_room_alias(room_alias_id!("#a:b.c"))
710 /// .await
711 /// .expect("We should be able to remove the room alias");
712 /// # anyhow::Ok(()) });
713 /// ```
714 pub fn mock_room_directory_remove_room_alias(
715 &self,
716 ) -> MockEndpoint<'_, RemoveRoomAliasEndpoint> {
717 let mock =
718 Mock::given(method("DELETE")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
719 self.mock_endpoint(mock, RemoveRoomAliasEndpoint)
720 }
721
722 /// Create a prebuilt mock for listing public rooms.
723 ///
724 /// # Examples
725 ///
726 /// ```
727 /// #
728 /// tokio_test::block_on(async {
729 /// use js_int::uint;
730 /// use ruma::directory::PublicRoomsChunkInit;
731 /// use matrix_sdk::room_directory_search::RoomDirectorySearch;
732 /// use matrix_sdk::{
733 /// ruma::{event_id, room_id},
734 /// test_utils::mocks::MatrixMockServer,
735 /// };
736 /// let mock_server = MatrixMockServer::new().await;
737 /// let client = mock_server.client_builder().build().await;
738 /// let event_id = event_id!("$id");
739 /// let room_id = room_id!("!room_id:localhost");
740 ///
741 /// let chunk = vec![PublicRoomsChunkInit {
742 /// num_joined_members: uint!(0),
743 /// room_id: room_id.to_owned(),
744 /// world_readable: true,
745 /// guest_can_join: true,
746 /// }.into()];
747 ///
748 /// mock_server.mock_public_rooms().ok(chunk, None, None, Some(20)).mock_once().mount().await;
749 /// let mut room_directory_search = RoomDirectorySearch::new(client);
750 ///
751 /// room_directory_search.search(Some("some-alias".to_owned()), 100, None)
752 /// .await
753 /// .expect("Room directory search failed");
754 ///
755 /// let (results, _) = room_directory_search.results();
756 /// assert_eq!(results.len(), 1);
757 /// assert_eq!(results.get(0).unwrap().room_id, room_id.to_owned());
758 /// # });
759 /// ```
760 pub fn mock_public_rooms(&self) -> MockEndpoint<'_, PublicRoomsEndpoint> {
761 let mock = Mock::given(method("POST")).and(path_regex(r"/_matrix/client/v3/publicRooms"));
762 self.mock_endpoint(mock, PublicRoomsEndpoint)
763 }
764
765 /// Create a prebuilt mock for setting a room's visibility in the room
766 /// directory.
767 ///
768 /// # Examples
769 ///
770 /// ```
771 /// # tokio_test::block_on(async {
772 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
773 /// use ruma::api::client::room::Visibility;
774 ///
775 /// let mock_server = MatrixMockServer::new().await;
776 /// let client = mock_server.client_builder().build().await;
777 ///
778 /// mock_server
779 /// .mock_room_directory_set_room_visibility()
780 /// .ok()
781 /// .mock_once()
782 /// .mount()
783 /// .await;
784 ///
785 /// let room = mock_server
786 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
787 /// .await;
788 ///
789 /// room.privacy_settings()
790 /// .update_room_visibility(Visibility::Private)
791 /// .await
792 /// .expect("We should be able to update the room's visibility");
793 /// # anyhow::Ok(()) });
794 /// ```
795 pub fn mock_room_directory_set_room_visibility(
796 &self,
797 ) -> MockEndpoint<'_, SetRoomVisibilityEndpoint> {
798 let mock = Mock::given(method("PUT"))
799 .and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$"));
800 self.mock_endpoint(mock, SetRoomVisibilityEndpoint)
801 }
802
803 /// Create a prebuilt mock for getting a room's visibility in the room
804 /// directory.
805 ///
806 /// # Examples
807 ///
808 /// ```
809 /// # tokio_test::block_on(async {
810 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
811 /// use ruma::api::client::room::Visibility;
812 ///
813 /// let mock_server = MatrixMockServer::new().await;
814 /// let client = mock_server.client_builder().build().await;
815 ///
816 /// mock_server
817 /// .mock_room_directory_get_room_visibility()
818 /// .ok(Visibility::Public)
819 /// .mock_once()
820 /// .mount()
821 /// .await;
822 ///
823 /// let room = mock_server
824 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
825 /// .await;
826 ///
827 /// let visibility = room
828 /// .privacy_settings()
829 /// .get_room_visibility()
830 /// .await
831 /// .expect("We should be able to get the room's visibility");
832 /// assert_eq!(visibility, Visibility::Public);
833 /// # anyhow::Ok(()) });
834 /// ```
835 pub fn mock_room_directory_get_room_visibility(
836 &self,
837 ) -> MockEndpoint<'_, GetRoomVisibilityEndpoint> {
838 let mock = Mock::given(method("GET"))
839 .and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$"));
840 self.mock_endpoint(mock, GetRoomVisibilityEndpoint)
841 }
842
843 /// Create a prebuilt mock for fetching information about key storage
844 /// backups.
845 ///
846 /// # Examples
847 ///
848 /// ```
849 /// # #[cfg(feature = "e2e-encryption")]
850 /// # {
851 /// # tokio_test::block_on(async {
852 /// use matrix_sdk::test_utils::mocks::MatrixMockServer;
853 ///
854 /// let mock_server = MatrixMockServer::new().await;
855 /// let client = mock_server.client_builder().build().await;
856 ///
857 /// mock_server.mock_room_keys_version().exists().expect(1).mount().await;
858 ///
859 /// let exists =
860 /// client.encryption().backups().fetch_exists_on_server().await.unwrap();
861 ///
862 /// assert!(exists);
863 /// # });
864 /// # }
865 /// ```
866 pub fn mock_room_keys_version(&self) -> MockEndpoint<'_, RoomKeysVersionEndpoint> {
867 let mock =
868 Mock::given(method("GET")).and(path_regex(r"_matrix/client/v3/room_keys/version"));
869 self.mock_endpoint(mock, RoomKeysVersionEndpoint).expect_default_access_token()
870 }
871
872 /// Create a prebuilt mock for adding key storage backups via POST
873 pub fn mock_add_room_keys_version(&self) -> MockEndpoint<'_, AddRoomKeysVersionEndpoint> {
874 let mock =
875 Mock::given(method("POST")).and(path_regex(r"_matrix/client/v3/room_keys/version"));
876 self.mock_endpoint(mock, AddRoomKeysVersionEndpoint).expect_default_access_token()
877 }
878
879 /// Create a prebuilt mock for adding key storage backups via POST
880 pub fn mock_delete_room_keys_version(&self) -> MockEndpoint<'_, DeleteRoomKeysVersionEndpoint> {
881 let mock = Mock::given(method("DELETE"))
882 .and(path_regex(r"_matrix/client/v3/room_keys/version/[^/]*"));
883 self.mock_endpoint(mock, DeleteRoomKeysVersionEndpoint).expect_default_access_token()
884 }
885
886 /// Creates a prebuilt mock for the `/sendToDevice` endpoint.
887 ///
888 /// This mock can be used to simulate sending to-device messages in tests.
889 /// # Examples
890 ///
891 /// ```
892 /// # #[cfg(feature = "e2e-encryption")]
893 /// # {
894 /// # tokio_test::block_on(async {
895 /// use std::collections::BTreeMap;
896 /// use matrix_sdk::{
897 /// ruma::{
898 /// serde::Raw,
899 /// api::client::to_device::send_event_to_device::v3::Request as ToDeviceRequest,
900 /// to_device::DeviceIdOrAllDevices,
901 /// user_id,owned_device_id
902 /// },
903 /// test_utils::mocks::MatrixMockServer,
904 /// };
905 /// use serde_json::json;
906 ///
907 /// let mock_server = MatrixMockServer::new().await;
908 /// let client = mock_server.client_builder().build().await;
909 ///
910 /// mock_server.mock_send_to_device().ok().mock_once().mount().await;
911 ///
912 /// let request = ToDeviceRequest::new_raw(
913 /// "m.custom.event".into(),
914 /// "txn_id".into(),
915 /// BTreeMap::from([
916 /// (user_id!("@alice:localhost").to_owned(), BTreeMap::from([(
917 /// DeviceIdOrAllDevices::AllDevices,
918 /// Raw::new(&ruma::events::AnyToDeviceEventContent::Dummy(ruma::events::dummy::ToDeviceDummyEventContent {})).unwrap(),
919 /// )])),
920 /// ])
921 /// );
922 ///
923 /// client
924 /// .send(request)
925 /// .await
926 /// .expect("We should be able to send a to-device message");
927 /// # anyhow::Ok(()) });
928 /// # }
929 /// ```
930 pub fn mock_send_to_device(&self) -> MockEndpoint<'_, SendToDeviceEndpoint> {
931 let mock =
932 Mock::given(method("PUT")).and(path_regex(r"^/_matrix/client/v3/sendToDevice/.*/.*"));
933 self.mock_endpoint(mock, SendToDeviceEndpoint).expect_default_access_token()
934 }
935
936 /// Create a prebuilt mock for getting the room members in a room.
937 ///
938 /// # Examples
939 ///
940 /// ```
941 /// # tokio_test::block_on(async {
942 /// use matrix_sdk::{
943 /// ruma::{event_id, room_id},
944 /// test_utils::mocks::MatrixMockServer,
945 /// };
946 /// use matrix_sdk_base::RoomMemberships;
947 /// use matrix_sdk_test::event_factory::EventFactory;
948 /// use ruma::{
949 /// events::room::member::{MembershipState, RoomMemberEventContent},
950 /// user_id,
951 /// };
952 /// let mock_server = MatrixMockServer::new().await;
953 /// let client = mock_server.client_builder().build().await;
954 /// let event_id = event_id!("$id");
955 /// let room_id = room_id!("!room_id:localhost");
956 ///
957 /// let f = EventFactory::new().room(room_id);
958 /// let alice_user_id = user_id!("@alice:b.c");
959 /// let alice_knock_event = f
960 /// .event(RoomMemberEventContent::new(MembershipState::Knock))
961 /// .event_id(event_id)
962 /// .sender(alice_user_id)
963 /// .state_key(alice_user_id)
964 /// .into_raw();
965 ///
966 /// mock_server
967 /// .mock_get_members()
968 /// .ok(vec![alice_knock_event])
969 /// .mock_once()
970 /// .mount()
971 /// .await;
972 /// let room = mock_server.sync_joined_room(&client, room_id).await;
973 ///
974 /// let members = room.members(RoomMemberships::all()).await.unwrap();
975 /// assert_eq!(members.len(), 1);
976 /// # });
977 /// ```
978 pub fn mock_get_members(&self) -> MockEndpoint<'_, GetRoomMembersEndpoint> {
979 let mock =
980 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/members$"));
981 self.mock_endpoint(mock, GetRoomMembersEndpoint)
982 }
983
984 /// Creates a prebuilt mock for inviting a user to a room by its id.
985 ///
986 /// # Examples
987 ///
988 /// ```
989 /// # use ruma::user_id;
990 /// tokio_test::block_on(async {
991 /// use matrix_sdk::{
992 /// ruma::room_id,
993 /// test_utils::mocks::MatrixMockServer,
994 /// };
995 ///
996 /// let mock_server = MatrixMockServer::new().await;
997 /// let client = mock_server.client_builder().build().await;
998 ///
999 /// mock_server.mock_invite_user_by_id().ok().mock_once().mount().await;
1000 ///
1001 /// let room = mock_server
1002 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1003 /// .await;
1004 ///
1005 /// room.invite_user_by_id(user_id!("@alice:localhost")).await.unwrap();
1006 /// # anyhow::Ok(()) });
1007 /// ```
1008 pub fn mock_invite_user_by_id(&self) -> MockEndpoint<'_, InviteUserByIdEndpoint> {
1009 let mock =
1010 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/invite$"));
1011 self.mock_endpoint(mock, InviteUserByIdEndpoint)
1012 }
1013
1014 /// Creates a prebuilt mock for kicking a user from a room.
1015 ///
1016 /// # Examples
1017 ///
1018 /// ```
1019 /// # use ruma::user_id;
1020 /// tokio_test::block_on(async {
1021 /// use matrix_sdk::{
1022 /// ruma::room_id,
1023 /// test_utils::mocks::MatrixMockServer,
1024 /// };
1025 ///
1026 /// let mock_server = MatrixMockServer::new().await;
1027 /// let client = mock_server.client_builder().build().await;
1028 ///
1029 /// mock_server.mock_kick_user().ok().mock_once().mount().await;
1030 ///
1031 /// let room = mock_server
1032 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1033 /// .await;
1034 ///
1035 /// room.kick_user(user_id!("@alice:localhost"), None).await.unwrap();
1036 /// # anyhow::Ok(()) });
1037 /// ```
1038 pub fn mock_kick_user(&self) -> MockEndpoint<'_, KickUserEndpoint> {
1039 let mock =
1040 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/kick"));
1041 self.mock_endpoint(mock, KickUserEndpoint)
1042 }
1043
1044 /// Creates a prebuilt mock for banning a user from a room.
1045 ///
1046 /// # Examples
1047 ///
1048 /// ```
1049 /// # use ruma::user_id;
1050 /// tokio_test::block_on(async {
1051 /// use matrix_sdk::{
1052 /// ruma::room_id,
1053 /// test_utils::mocks::MatrixMockServer,
1054 /// };
1055 ///
1056 /// let mock_server = MatrixMockServer::new().await;
1057 /// let client = mock_server.client_builder().build().await;
1058 ///
1059 /// mock_server.mock_ban_user().ok().mock_once().mount().await;
1060 ///
1061 /// let room = mock_server
1062 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1063 /// .await;
1064 ///
1065 /// room.ban_user(user_id!("@alice:localhost"), None).await.unwrap();
1066 /// # anyhow::Ok(()) });
1067 /// ```
1068 pub fn mock_ban_user(&self) -> MockEndpoint<'_, BanUserEndpoint> {
1069 let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/ban"));
1070 self.mock_endpoint(mock, BanUserEndpoint)
1071 }
1072
1073 /// Creates a prebuilt mock for the `/_matrix/client/versions` endpoint.
1074 pub fn mock_versions(&self) -> MockEndpoint<'_, VersionsEndpoint> {
1075 let mock = Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/versions"));
1076 self.mock_endpoint(mock, VersionsEndpoint)
1077 }
1078
1079 /// Creates a prebuilt mock for the room summary endpoint [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266).
1080 pub fn mock_room_summary(&self) -> MockEndpoint<'_, RoomSummaryEndpoint> {
1081 let mock = Mock::given(method("GET"))
1082 .and(path_regex(r"^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary"));
1083 self.mock_endpoint(mock, RoomSummaryEndpoint)
1084 }
1085
1086 /// Creates a prebuilt mock for the endpoint used to set a room's pinned
1087 /// events.
1088 pub fn mock_set_room_pinned_events(&self) -> MockEndpoint<'_, SetRoomPinnedEventsEndpoint> {
1089 let mock = Mock::given(method("PUT"))
1090 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.room.pinned_events/.*?"));
1091 self.mock_endpoint(mock, SetRoomPinnedEventsEndpoint).expect_default_access_token()
1092 }
1093
1094 /// Creates a prebuilt mock for the endpoint used to get information about
1095 /// the owner of the given access token.
1096 ///
1097 /// If no access token is provided, the access token to match is `"1234"`,
1098 /// which matches the default value in the mock data.
1099 pub fn mock_who_am_i(&self) -> MockEndpoint<'_, WhoAmIEndpoint> {
1100 let mock =
1101 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/account/whoami"));
1102 self.mock_endpoint(mock, WhoAmIEndpoint).expect_default_access_token()
1103 }
1104
1105 /// Creates a prebuilt mock for the endpoint used to publish end-to-end
1106 /// encryption keys.
1107 pub fn mock_upload_keys(&self) -> MockEndpoint<'_, UploadKeysEndpoint> {
1108 let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/keys/upload"));
1109 self.mock_endpoint(mock, UploadKeysEndpoint).expect_default_access_token()
1110 }
1111
1112 /// Creates a prebuilt mock for the endpoint used to query end-to-end
1113 /// encryption keys.
1114 pub fn mock_query_keys(&self) -> MockEndpoint<'_, QueryKeysEndpoint> {
1115 let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/keys/query"));
1116 self.mock_endpoint(mock, QueryKeysEndpoint).expect_default_access_token()
1117 }
1118
1119 /// Creates a prebuilt mock for the endpoint used to discover the URL of a
1120 /// homeserver.
1121 pub fn mock_well_known(&self) -> MockEndpoint<'_, WellKnownEndpoint> {
1122 let mock = Mock::given(method("GET")).and(path_regex(r"^/.well-known/matrix/client"));
1123 self.mock_endpoint(mock, WellKnownEndpoint)
1124 }
1125
1126 /// Creates a prebuilt mock for the endpoint used to publish cross-signing
1127 /// keys.
1128 pub fn mock_upload_cross_signing_keys(
1129 &self,
1130 ) -> MockEndpoint<'_, UploadCrossSigningKeysEndpoint> {
1131 let mock = Mock::given(method("POST"))
1132 .and(path_regex(r"^/_matrix/client/v3/keys/device_signing/upload"));
1133 self.mock_endpoint(mock, UploadCrossSigningKeysEndpoint).expect_default_access_token()
1134 }
1135
1136 /// Creates a prebuilt mock for the endpoint used to publish cross-signing
1137 /// signatures.
1138 pub fn mock_upload_cross_signing_signatures(
1139 &self,
1140 ) -> MockEndpoint<'_, UploadCrossSigningSignaturesEndpoint> {
1141 let mock = Mock::given(method("POST"))
1142 .and(path_regex(r"^/_matrix/client/v3/keys/signatures/upload"));
1143 self.mock_endpoint(mock, UploadCrossSigningSignaturesEndpoint).expect_default_access_token()
1144 }
1145
1146 /// Creates a prebuilt mock for the endpoint used to leave a room.
1147 pub fn mock_room_leave(&self) -> MockEndpoint<'_, RoomLeaveEndpoint> {
1148 let mock =
1149 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/leave"));
1150 self.mock_endpoint(mock, RoomLeaveEndpoint).expect_default_access_token()
1151 }
1152
1153 /// Creates a prebuilt mock for the endpoint used to forget a room.
1154 pub fn mock_room_forget(&self) -> MockEndpoint<'_, RoomForgetEndpoint> {
1155 let mock =
1156 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/forget"));
1157 self.mock_endpoint(mock, RoomForgetEndpoint).expect_default_access_token()
1158 }
1159
1160 /// Create a prebuilt mock for the endpoint use to log out a session.
1161 pub fn mock_logout(&self) -> MockEndpoint<'_, LogoutEndpoint> {
1162 let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/logout"));
1163 self.mock_endpoint(mock, LogoutEndpoint).expect_default_access_token()
1164 }
1165
1166 /// Create a prebuilt mock for the endpoint used to get the list of thread
1167 /// roots.
1168 pub fn mock_room_threads(&self) -> MockEndpoint<'_, RoomThreadsEndpoint> {
1169 let mock =
1170 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v1/rooms/.*/threads$"));
1171 self.mock_endpoint(mock, RoomThreadsEndpoint).expect_default_access_token()
1172 }
1173
1174 /// Create a prebuilt mock for the endpoint used to get the related events.
1175 pub fn mock_room_relations(&self) -> MockEndpoint<'_, RoomRelationsEndpoint> {
1176 // Routing happens in the final method ok(), since it can get complicated.
1177 let mock = Mock::given(method("GET"));
1178 self.mock_endpoint(mock, RoomRelationsEndpoint::default()).expect_default_access_token()
1179 }
1180
1181 /// Create a prebuilt mock for the endpoint used to get the global account
1182 /// data.
1183 ///
1184 /// # Examples
1185 ///
1186 /// ```
1187 /// tokio_test::block_on(async {
1188 /// use js_int::uint;
1189 /// use matrix_sdk::test_utils::mocks::MatrixMockServer;
1190 ///
1191 /// let mock_server = MatrixMockServer::new().await;
1192 /// let client = mock_server.client_builder().build().await;
1193 ///
1194 /// mock_server.mock_get_recent_emojis().ok(
1195 /// client.user_id().unwrap(),
1196 /// vec![(":)".to_string(), uint!(1))]
1197 /// )
1198 /// .mock_once()
1199 /// .mount()
1200 /// .await;
1201 ///
1202 /// client.account().fetch_media_preview_config_event_content().await.unwrap();
1203 ///
1204 /// # anyhow::Ok(()) });
1205 /// ```
1206 #[cfg(feature = "experimental-element-recent-emojis")]
1207 pub fn mock_get_recent_emojis(&self) -> MockEndpoint<'_, GetRecentEmojisEndpoint> {
1208 let mock = Mock::given(method("GET"));
1209 self.mock_endpoint(mock, GetRecentEmojisEndpoint).expect_default_access_token()
1210 }
1211
1212 /// Create a prebuilt mock for the endpoint that updates the global account
1213 /// data.
1214 ///
1215 /// # Examples
1216 ///
1217 /// ```
1218 /// tokio_test::block_on(async {
1219 /// use js_int::uint;
1220 /// use matrix_sdk::test_utils::mocks::MatrixMockServer;
1221 /// use ruma::user_id;
1222 ///
1223 /// let mock_server = MatrixMockServer::new().await;
1224 /// let client = mock_server.client_builder().build().await;
1225 /// let user_id = client.user_id().unwrap();
1226 ///
1227 /// mock.server.mock_get_recent_emojis()
1228 /// .ok(user_id, vec![(":D".to_string(), uint!(1))])
1229 /// .mock_once()
1230 /// .mount()
1231 /// .await;
1232 /// mock_server.mock_add_recent_emojis()
1233 /// .ok(user_id)
1234 /// .mock_once()
1235 /// .mount()
1236 /// .await;
1237 /// // Calls both get and update recent emoji endpoints, with an update value
1238 /// client.account().add_recent_emoji(":D").await.unwrap();
1239 ///
1240 /// # anyhow::Ok(()) });
1241 /// ```
1242 #[cfg(feature = "experimental-element-recent-emojis")]
1243 pub fn mock_add_recent_emojis(&self) -> MockEndpoint<'_, UpdateRecentEmojisEndpoint> {
1244 let mock = Mock::given(method("PUT"));
1245 self.mock_endpoint(mock, UpdateRecentEmojisEndpoint::new()).expect_default_access_token()
1246 }
1247
1248 /// Create a prebuilt mock for the endpoint used to send a single receipt.
1249 pub fn mock_send_receipt(
1250 &self,
1251 receipt_type: ReceiptType,
1252 ) -> MockEndpoint<'_, ReceiptEndpoint> {
1253 let mock = Mock::given(method("POST"))
1254 .and(path_regex(format!("^/_matrix/client/v3/rooms/.*/receipt/{receipt_type}/")));
1255 self.mock_endpoint(mock, ReceiptEndpoint).expect_default_access_token()
1256 }
1257
1258 /// Create a prebuilt mock for the endpoint used to send multiple receipts.
1259 pub fn mock_send_read_markers(&self) -> MockEndpoint<'_, ReadMarkersEndpoint> {
1260 let mock = Mock::given(method("POST"))
1261 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/read_markers"));
1262 self.mock_endpoint(mock, ReadMarkersEndpoint).expect_default_access_token()
1263 }
1264
1265 /// Create a prebuilt mock for the endpoint used to set room account data.
1266 pub fn mock_set_room_account_data(
1267 &self,
1268 data_type: RoomAccountDataEventType,
1269 ) -> MockEndpoint<'_, RoomAccountDataEndpoint> {
1270 let mock = Mock::given(method("PUT")).and(path_regex(format!(
1271 "^/_matrix/client/v3/user/.*/rooms/.*/account_data/{data_type}"
1272 )));
1273 self.mock_endpoint(mock, RoomAccountDataEndpoint).expect_default_access_token()
1274 }
1275
1276 /// Create a prebuilt mock for the endpoint used to get the media config of
1277 /// the homeserver that requires authentication.
1278 pub fn mock_authenticated_media_config(
1279 &self,
1280 ) -> MockEndpoint<'_, AuthenticatedMediaConfigEndpoint> {
1281 let mock = Mock::given(method("GET")).and(path("/_matrix/client/v1/media/config"));
1282 self.mock_endpoint(mock, AuthenticatedMediaConfigEndpoint)
1283 }
1284
1285 /// Create a prebuilt mock for the endpoint used to get the media config of
1286 /// the homeserver without requiring authentication.
1287 pub fn mock_media_config(&self) -> MockEndpoint<'_, MediaConfigEndpoint> {
1288 let mock = Mock::given(method("GET")).and(path("/_matrix/media/v3/config"));
1289 self.mock_endpoint(mock, MediaConfigEndpoint)
1290 }
1291
1292 /// Create a prebuilt mock for the endpoint used to log into a session.
1293 pub fn mock_login(&self) -> MockEndpoint<'_, LoginEndpoint> {
1294 let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/login"));
1295 self.mock_endpoint(mock, LoginEndpoint)
1296 }
1297
1298 /// Create a prebuilt mock for the endpoint used to list the devices of a
1299 /// user.
1300 pub fn mock_devices(&self) -> MockEndpoint<'_, DevicesEndpoint> {
1301 let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/devices"));
1302 self.mock_endpoint(mock, DevicesEndpoint).expect_default_access_token()
1303 }
1304
1305 /// Create a prebuilt mock for the endpoint used to search in the user
1306 /// directory.
1307 pub fn mock_user_directory(&self) -> MockEndpoint<'_, UserDirectoryEndpoint> {
1308 let mock = Mock::given(method("POST"))
1309 .and(path("/_matrix/client/v3/user_directory/search"))
1310 .and(body_json(&*test_json::search_users::SEARCH_USERS_REQUEST));
1311 self.mock_endpoint(mock, UserDirectoryEndpoint).expect_default_access_token()
1312 }
1313
1314 /// Create a prebuilt mock for the endpoint used to create a new room.
1315 pub fn mock_create_room(&self) -> MockEndpoint<'_, CreateRoomEndpoint> {
1316 let mock = Mock::given(method("POST")).and(path("/_matrix/client/v3/createRoom"));
1317 self.mock_endpoint(mock, CreateRoomEndpoint).expect_default_access_token()
1318 }
1319
1320 /// Create a prebuilt mock for the endpoint used to upgrade a room.
1321 pub fn mock_upgrade_room(&self) -> MockEndpoint<'_, UpgradeRoomEndpoint> {
1322 let mock =
1323 Mock::given(method("POST")).and(path_regex("/_matrix/client/v3/rooms/.*/upgrade"));
1324 self.mock_endpoint(mock, UpgradeRoomEndpoint).expect_default_access_token()
1325 }
1326
1327 /// Create a prebuilt mock for the endpoint used to pre-allocate a MXC URI
1328 /// for a media file.
1329 pub fn mock_media_allocate(&self) -> MockEndpoint<'_, MediaAllocateEndpoint> {
1330 let mock = Mock::given(method("POST")).and(path("/_matrix/media/v1/create"));
1331 self.mock_endpoint(mock, MediaAllocateEndpoint)
1332 }
1333
1334 /// Create a prebuilt mock for the endpoint used to upload a media file with
1335 /// a pre-allocated MXC URI.
1336 pub fn mock_media_allocated_upload(
1337 &self,
1338 server_name: &str,
1339 media_id: &str,
1340 ) -> MockEndpoint<'_, MediaAllocatedUploadEndpoint> {
1341 let mock = Mock::given(method("PUT"))
1342 .and(path(format!("/_matrix/media/v3/upload/{server_name}/{media_id}")));
1343 self.mock_endpoint(mock, MediaAllocatedUploadEndpoint)
1344 }
1345
1346 /// Create a prebuilt mock for the endpoint used to download a media file
1347 /// without requiring authentication.
1348 pub fn mock_media_download(&self) -> MockEndpoint<'_, MediaDownloadEndpoint> {
1349 let mock = Mock::given(method("GET")).and(path_regex("^/_matrix/media/v3/download/"));
1350 self.mock_endpoint(mock, MediaDownloadEndpoint)
1351 }
1352
1353 /// Create a prebuilt mock for the endpoint used to download a thumbnail of
1354 /// a media file without requiring authentication.
1355 pub fn mock_media_thumbnail(
1356 &self,
1357 resize_method: Method,
1358 width: u16,
1359 height: u16,
1360 animated: bool,
1361 ) -> MockEndpoint<'_, MediaThumbnailEndpoint> {
1362 let mock = Mock::given(method("GET"))
1363 .and(path_regex("^/_matrix/media/v3/thumbnail/"))
1364 .and(query_param("method", resize_method.as_str()))
1365 .and(query_param("width", width.to_string()))
1366 .and(query_param("height", height.to_string()))
1367 .and(query_param("animated", animated.to_string()));
1368 self.mock_endpoint(mock, MediaThumbnailEndpoint)
1369 }
1370
1371 /// Create a prebuilt mock for the endpoint used to download a media file
1372 /// that requires authentication.
1373 pub fn mock_authed_media_download(&self) -> MockEndpoint<'_, AuthedMediaDownloadEndpoint> {
1374 let mock =
1375 Mock::given(method("GET")).and(path_regex("^/_matrix/client/v1/media/download/"));
1376 self.mock_endpoint(mock, AuthedMediaDownloadEndpoint).expect_default_access_token()
1377 }
1378
1379 /// Create a prebuilt mock for the endpoint used to download a thumbnail of
1380 /// a media file that requires authentication.
1381 pub fn mock_authed_media_thumbnail(
1382 &self,
1383 resize_method: Method,
1384 width: u16,
1385 height: u16,
1386 animated: bool,
1387 ) -> MockEndpoint<'_, AuthedMediaThumbnailEndpoint> {
1388 let mock = Mock::given(method("GET"))
1389 .and(path_regex("^/_matrix/client/v1/media/thumbnail/"))
1390 .and(query_param("method", resize_method.as_str()))
1391 .and(query_param("width", width.to_string()))
1392 .and(query_param("height", height.to_string()))
1393 .and(query_param("animated", animated.to_string()));
1394 self.mock_endpoint(mock, AuthedMediaThumbnailEndpoint).expect_default_access_token()
1395 }
1396
1397 /// Create a prebuilt mock for the endpoint used to get a single thread
1398 /// subscription status in a given room.
1399 pub fn mock_room_get_thread_subscription(
1400 &self,
1401 ) -> MockEndpoint<'_, RoomGetThreadSubscriptionEndpoint> {
1402 let mock = Mock::given(method("GET"));
1403 self.mock_endpoint(mock, RoomGetThreadSubscriptionEndpoint::default())
1404 .expect_default_access_token()
1405 }
1406
1407 /// Create a prebuilt mock for the endpoint used to define a thread
1408 /// subscription in a given room.
1409 pub fn mock_room_put_thread_subscription(
1410 &self,
1411 ) -> MockEndpoint<'_, RoomPutThreadSubscriptionEndpoint> {
1412 let mock = Mock::given(method("PUT"));
1413 self.mock_endpoint(mock, RoomPutThreadSubscriptionEndpoint::default())
1414 .expect_default_access_token()
1415 }
1416
1417 /// Create a prebuilt mock for the endpoint used to delete a thread
1418 /// subscription in a given room.
1419 pub fn mock_room_delete_thread_subscription(
1420 &self,
1421 ) -> MockEndpoint<'_, RoomDeleteThreadSubscriptionEndpoint> {
1422 let mock = Mock::given(method("DELETE"));
1423 self.mock_endpoint(mock, RoomDeleteThreadSubscriptionEndpoint::default())
1424 .expect_default_access_token()
1425 }
1426
1427 /// Create a prebuilt mock for the endpoint used to enable a push rule.
1428 pub fn mock_enable_push_rule(
1429 &self,
1430 kind: RuleKind,
1431 rule_id: impl AsRef<str>,
1432 ) -> MockEndpoint<'_, EnablePushRuleEndpoint> {
1433 let rule_id = rule_id.as_ref();
1434 let mock = Mock::given(method("PUT")).and(path_regex(format!(
1435 "^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}/enabled",
1436 )));
1437 self.mock_endpoint(mock, EnablePushRuleEndpoint).expect_default_access_token()
1438 }
1439
1440 /// Create a prebuilt mock for the endpoint used to set push rules actions.
1441 pub fn mock_set_push_rules_actions(
1442 &self,
1443 kind: RuleKind,
1444 rule_id: PushRuleIdSpec<'_>,
1445 ) -> MockEndpoint<'_, SetPushRulesActionsEndpoint> {
1446 let rule_id = rule_id.to_path();
1447 let mock = Mock::given(method("PUT")).and(path_regex(format!(
1448 "^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}/actions",
1449 )));
1450 self.mock_endpoint(mock, SetPushRulesActionsEndpoint).expect_default_access_token()
1451 }
1452
1453 /// Create a prebuilt mock for the endpoint used to set push rules.
1454 pub fn mock_set_push_rules(
1455 &self,
1456 kind: RuleKind,
1457 rule_id: PushRuleIdSpec<'_>,
1458 ) -> MockEndpoint<'_, SetPushRulesEndpoint> {
1459 let rule_id = rule_id.to_path();
1460 let mock = Mock::given(method("PUT"))
1461 .and(path_regex(format!("^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}$",)));
1462 self.mock_endpoint(mock, SetPushRulesEndpoint).expect_default_access_token()
1463 }
1464
1465 /// Create a prebuilt mock for the endpoint used to delete push rules.
1466 pub fn mock_delete_push_rules(
1467 &self,
1468 kind: RuleKind,
1469 rule_id: PushRuleIdSpec<'_>,
1470 ) -> MockEndpoint<'_, DeletePushRulesEndpoint> {
1471 let rule_id = rule_id.to_path();
1472 let mock = Mock::given(method("DELETE"))
1473 .and(path_regex(format!("^/_matrix/client/v3/pushrules/global/{kind}/{rule_id}$",)));
1474 self.mock_endpoint(mock, DeletePushRulesEndpoint).expect_default_access_token()
1475 }
1476
1477 /// Create a prebuilt mock for the federation version endpoint.
1478 pub fn mock_federation_version(&self) -> MockEndpoint<'_, FederationVersionEndpoint> {
1479 let mock = Mock::given(method("GET")).and(path("/_matrix/federation/v1/version"));
1480 self.mock_endpoint(mock, FederationVersionEndpoint)
1481 }
1482
1483 /// Create a prebuilt mock for the endpoint used to get all thread
1484 /// subscriptions across all rooms.
1485 pub fn mock_get_thread_subscriptions(
1486 &self,
1487 ) -> MockEndpoint<'_, GetThreadSubscriptionsEndpoint> {
1488 let mock = Mock::given(method("GET"))
1489 .and(path_regex(r"^/_matrix/client/unstable/io.element.msc4308/thread_subscriptions$"));
1490 self.mock_endpoint(mock, GetThreadSubscriptionsEndpoint::default())
1491 .expect_default_access_token()
1492 }
1493
1494 /// Create a prebuilt mock for the endpoint used to retrieve a space tree
1495 pub fn mock_get_hierarchy(&self) -> MockEndpoint<'_, GetHierarchyEndpoint> {
1496 let mock =
1497 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v1/rooms/.*/hierarchy"));
1498 self.mock_endpoint(mock, GetHierarchyEndpoint).expect_default_access_token()
1499 }
1500}
1501
1502/// A specification for a push rule ID.
1503pub enum PushRuleIdSpec<'a> {
1504 /// A precise rule ID.
1505 Some(&'a str),
1506 /// Any rule ID should match.
1507 Any,
1508}
1509
1510impl<'a> PushRuleIdSpec<'a> {
1511 /// Convert this [`PushRuleIdSpec`] to a path.
1512 pub fn to_path(&self) -> &str {
1513 match self {
1514 PushRuleIdSpec::Some(id) => id,
1515 PushRuleIdSpec::Any => "[^/]*",
1516 }
1517 }
1518}
1519
1520/// Parameter to [`MatrixMockServer::sync_room`].
1521pub enum AnyRoomBuilder {
1522 /// A room we've been invited to.
1523 Invited(InvitedRoomBuilder),
1524 /// A room we've joined.
1525 Joined(JoinedRoomBuilder),
1526 /// A room we've left.
1527 Left(LeftRoomBuilder),
1528 /// A room we've knocked to.
1529 Knocked(KnockedRoomBuilder),
1530}
1531
1532impl AnyRoomBuilder {
1533 /// Get the [`RoomId`] of the room this [`AnyRoomBuilder`] will create.
1534 fn room_id(&self) -> &RoomId {
1535 match self {
1536 AnyRoomBuilder::Invited(r) => r.room_id(),
1537 AnyRoomBuilder::Joined(r) => r.room_id(),
1538 AnyRoomBuilder::Left(r) => r.room_id(),
1539 AnyRoomBuilder::Knocked(r) => r.room_id(),
1540 }
1541 }
1542}
1543
1544impl From<InvitedRoomBuilder> for AnyRoomBuilder {
1545 fn from(val: InvitedRoomBuilder) -> AnyRoomBuilder {
1546 AnyRoomBuilder::Invited(val)
1547 }
1548}
1549
1550impl From<JoinedRoomBuilder> for AnyRoomBuilder {
1551 fn from(val: JoinedRoomBuilder) -> AnyRoomBuilder {
1552 AnyRoomBuilder::Joined(val)
1553 }
1554}
1555
1556impl From<LeftRoomBuilder> for AnyRoomBuilder {
1557 fn from(val: LeftRoomBuilder) -> AnyRoomBuilder {
1558 AnyRoomBuilder::Left(val)
1559 }
1560}
1561
1562impl From<KnockedRoomBuilder> for AnyRoomBuilder {
1563 fn from(val: KnockedRoomBuilder) -> AnyRoomBuilder {
1564 AnyRoomBuilder::Knocked(val)
1565 }
1566}
1567
1568/// The [path percent-encode set] as defined in the WHATWG URL standard + `/`
1569/// since we always encode single segments of the path.
1570///
1571/// [path percent-encode set]: https://url.spec.whatwg.org/#path-percent-encode-set
1572///
1573/// Copied from Ruma:
1574/// https://github.com/ruma/ruma/blob/e4cb409ff3aaa16f31a7fe1e61fee43b2d144f7b/crates/ruma-common/src/percent_encode.rs#L7
1575const PATH_PERCENT_ENCODE_SET: &AsciiSet = &CONTROLS
1576 .add(b' ')
1577 .add(b'"')
1578 .add(b'#')
1579 .add(b'<')
1580 .add(b'>')
1581 .add(b'?')
1582 .add(b'`')
1583 .add(b'{')
1584 .add(b'}')
1585 .add(b'/');
1586
1587fn percent_encoded_path(path: &str) -> String {
1588 percent_encoding::utf8_percent_encode(path, PATH_PERCENT_ENCODE_SET).to_string()
1589}
1590
1591/// A wrapper for a [`Mock`] as well as a [`MockServer`], allowing us to call
1592/// [`Mock::mount`] or [`Mock::mount_as_scoped`] without having to pass the
1593/// [`MockServer`] reference (i.e. call `mount()` instead of `mount(&server)`).
1594pub struct MatrixMock<'a> {
1595 pub(super) mock: Mock,
1596 pub(super) server: &'a MockServer,
1597}
1598
1599impl MatrixMock<'_> {
1600 /// Set an expectation on the number of times this [`MatrixMock`] should
1601 /// match in the current test case.
1602 ///
1603 /// Expectations are verified when the server is shutting down: if
1604 /// the expectation is not satisfied, the [`MatrixMockServer`] will panic
1605 /// and the `error_message` is shown.
1606 ///
1607 /// By default, no expectation is set for [`MatrixMock`]s.
1608 pub fn expect<T: Into<Times>>(self, num_calls: T) -> Self {
1609 Self { mock: self.mock.expect(num_calls), ..self }
1610 }
1611
1612 /// Assign a name to your mock.
1613 ///
1614 /// The mock name will be used in error messages (e.g. if the mock
1615 /// expectation is not satisfied) and debug logs to help you identify
1616 /// what failed.
1617 pub fn named(self, name: impl Into<String>) -> Self {
1618 Self { mock: self.mock.named(name), ..self }
1619 }
1620
1621 /// Respond to a response of this endpoint exactly once.
1622 ///
1623 /// After it's been called, subsequent responses will hit the next handler
1624 /// or a 404.
1625 ///
1626 /// Also verifies that it's been called once.
1627 pub fn mock_once(self) -> Self {
1628 Self { mock: self.mock.up_to_n_times(1).expect(1), ..self }
1629 }
1630
1631 /// Makes sure the endpoint is never reached.
1632 pub fn never(self) -> Self {
1633 Self { mock: self.mock.expect(0), ..self }
1634 }
1635
1636 /// Specify an upper limit to the number of times you would like this
1637 /// [`MatrixMock`] to respond to incoming requests that satisfy the
1638 /// conditions imposed by your matchers.
1639 pub fn up_to_n_times(self, num: u64) -> Self {
1640 Self { mock: self.mock.up_to_n_times(num), ..self }
1641 }
1642
1643 /// Mount a [`MatrixMock`] on the attached server.
1644 ///
1645 /// The [`MatrixMock`] will remain active until the [`MatrixMockServer`] is
1646 /// shut down. If you want to control or limit how long your
1647 /// [`MatrixMock`] stays active, check out [`Self::mount_as_scoped`].
1648 pub async fn mount(self) {
1649 self.mock.mount(self.server).await;
1650 }
1651
1652 /// Mount a [`MatrixMock`] as **scoped** on the attached server.
1653 ///
1654 /// When using [`Self::mount`], your [`MatrixMock`]s will be active until
1655 /// the [`MatrixMockServer`] is shut down.
1656 ///
1657 /// When using `mount_as_scoped`, your [`MatrixMock`]s will be active as
1658 /// long as the returned [`MockGuard`] is not dropped.
1659 ///
1660 /// When the returned [`MockGuard`] is dropped, [`MatrixMockServer`] will
1661 /// verify that the expectations set on the scoped [`MatrixMock`] were
1662 /// verified - if not, it will panic.
1663 pub async fn mount_as_scoped(self) -> MockGuard {
1664 self.mock.mount_as_scoped(self.server).await
1665 }
1666}
1667
1668/// Generic mocked endpoint, with useful common helpers.
1669pub struct MockEndpoint<'a, T> {
1670 server: &'a MockServer,
1671 mock: MockBuilder,
1672 endpoint: T,
1673 expected_access_token: ExpectedAccessToken,
1674}
1675
1676impl<'a, T> MockEndpoint<'a, T> {
1677 fn new(server: &'a MockServer, mock: MockBuilder, endpoint: T) -> Self {
1678 Self { server, mock, endpoint, expected_access_token: ExpectedAccessToken::None }
1679 }
1680
1681 /// Expect authentication with the default access token on this endpoint.
1682 pub fn expect_default_access_token(mut self) -> Self {
1683 self.expected_access_token = ExpectedAccessToken::Default;
1684 self
1685 }
1686
1687 /// Expect authentication with the given access token on this endpoint.
1688 pub fn expect_access_token(mut self, access_token: &'static str) -> Self {
1689 self.expected_access_token = ExpectedAccessToken::Custom(access_token);
1690 self
1691 }
1692
1693 /// Don't expect authentication with an access token on this endpoint.
1694 ///
1695 /// This should be used to override the default behavior of the endpoint,
1696 /// when the access token is unknown for example.
1697 pub fn do_not_expect_access_token(mut self) -> Self {
1698 self.expected_access_token = ExpectedAccessToken::None;
1699 self
1700 }
1701
1702 /// Specify how to respond to a query (viz., like
1703 /// [`MockBuilder::respond_with`] does), when other predefined responses
1704 /// aren't sufficient.
1705 ///
1706 /// # Examples
1707 ///
1708 /// ```
1709 /// # tokio_test::block_on(async {
1710 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1711 /// use serde_json::json;
1712 /// use wiremock::ResponseTemplate;
1713 ///
1714 /// let mock_server = MatrixMockServer::new().await;
1715 /// let client = mock_server.client_builder().build().await;
1716 ///
1717 /// mock_server.mock_room_state_encryption().plain().mount().await;
1718 ///
1719 /// let room = mock_server
1720 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1721 /// .await;
1722 ///
1723 /// let event_id = event_id!("$some_id");
1724 /// mock_server
1725 /// .mock_room_send()
1726 /// .respond_with(
1727 /// ResponseTemplate::new(429)
1728 /// .insert_header("Retry-After", "100")
1729 /// .set_body_json(json!({
1730 /// "errcode": "M_LIMIT_EXCEEDED",
1731 /// "custom_field": "with custom data",
1732 /// })))
1733 /// .expect(1)
1734 /// .mount()
1735 /// .await;
1736 ///
1737 /// room
1738 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1739 /// .await
1740 /// .expect_err("The sending of the event should fail");
1741 /// # anyhow::Ok(()) });
1742 /// ```
1743 pub fn respond_with<R: Respond + 'static>(self, func: R) -> MatrixMock<'a> {
1744 let mock = self
1745 .expected_access_token
1746 .maybe_match_authorization_header(self.mock)
1747 .respond_with(func);
1748 MatrixMock { mock, server: self.server }
1749 }
1750
1751 /// Returns a send endpoint that emulates a transient failure, i.e responds
1752 /// with error 500.
1753 ///
1754 /// # Examples
1755 /// ```
1756 /// # tokio_test::block_on(async {
1757 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1758 /// use serde_json::json;
1759 ///
1760 /// let mock_server = MatrixMockServer::new().await;
1761 /// let client = mock_server.client_builder().build().await;
1762 ///
1763 /// mock_server.mock_room_state_encryption().plain().mount().await;
1764 ///
1765 /// let room = mock_server
1766 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1767 /// .await;
1768 ///
1769 /// mock_server
1770 /// .mock_room_send()
1771 /// .error500()
1772 /// .expect(1)
1773 /// .mount()
1774 /// .await;
1775 ///
1776 /// room
1777 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1778 /// .await.expect_err("The sending of the event should have failed");
1779 /// # anyhow::Ok(()) });
1780 /// ```
1781 pub fn error500(self) -> MatrixMock<'a> {
1782 self.respond_with(ResponseTemplate::new(500))
1783 }
1784
1785 /// Internal helper to return an `{ event_id }` JSON struct along with a 200
1786 /// ok response.
1787 fn ok_with_event_id(self, event_id: OwnedEventId) -> MatrixMock<'a> {
1788 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })))
1789 }
1790
1791 /// Internal helper to return a 200 OK response with an empty JSON object in
1792 /// the body.
1793 fn ok_empty_json(self) -> MatrixMock<'a> {
1794 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
1795 }
1796
1797 /// Returns an endpoint that emulates a permanent failure error (e.g. event
1798 /// is too large).
1799 ///
1800 /// # Examples
1801 /// ```
1802 /// # tokio_test::block_on(async {
1803 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1804 /// use serde_json::json;
1805 ///
1806 /// let mock_server = MatrixMockServer::new().await;
1807 /// let client = mock_server.client_builder().build().await;
1808 ///
1809 /// mock_server.mock_room_state_encryption().plain().mount().await;
1810 ///
1811 /// let room = mock_server
1812 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1813 /// .await;
1814 ///
1815 /// mock_server
1816 /// .mock_room_send()
1817 /// .error_too_large()
1818 /// .expect(1)
1819 /// .mount()
1820 /// .await;
1821 ///
1822 /// room
1823 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1824 /// .await.expect_err("The sending of the event should have failed");
1825 /// # anyhow::Ok(()) });
1826 /// ```
1827 pub fn error_too_large(self) -> MatrixMock<'a> {
1828 self.respond_with(ResponseTemplate::new(413).set_body_json(json!({
1829 // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response
1830 "errcode": "M_TOO_LARGE",
1831 })))
1832 }
1833}
1834
1835/// The access token to expect on an endpoint.
1836enum ExpectedAccessToken {
1837 /// We don't expect an access token.
1838 None,
1839
1840 /// We expect the default access token.
1841 Default,
1842
1843 /// We expect the given access token.
1844 Custom(&'static str),
1845}
1846
1847impl ExpectedAccessToken {
1848 /// Match an `Authorization` header on the given mock if one is expected.
1849 fn maybe_match_authorization_header(&self, mock: MockBuilder) -> MockBuilder {
1850 let token = match self {
1851 Self::None => return mock,
1852 Self::Default => "1234",
1853 Self::Custom(token) => token,
1854 };
1855 mock.and(header(http::header::AUTHORIZATION, format!("Bearer {token}")))
1856 }
1857}
1858
1859/// A prebuilt mock for sending a message like event in a room.
1860pub struct RoomSendEndpoint;
1861
1862impl<'a> MockEndpoint<'a, RoomSendEndpoint> {
1863 /// Ensures that the body of the request is a superset of the provided
1864 /// `body` parameter.
1865 ///
1866 /// # Examples
1867 /// ```
1868 /// # tokio_test::block_on(async {
1869 /// use matrix_sdk::{
1870 /// ruma::{room_id, event_id, events::room::message::RoomMessageEventContent},
1871 /// test_utils::mocks::MatrixMockServer
1872 /// };
1873 /// use serde_json::json;
1874 ///
1875 /// let mock_server = MatrixMockServer::new().await;
1876 /// let client = mock_server.client_builder().build().await;
1877 ///
1878 /// mock_server.mock_room_state_encryption().plain().mount().await;
1879 ///
1880 /// let room = mock_server
1881 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1882 /// .await;
1883 ///
1884 /// let event_id = event_id!("$some_id");
1885 /// mock_server
1886 /// .mock_room_send()
1887 /// .body_matches_partial_json(json!({
1888 /// "body": "Hello world",
1889 /// }))
1890 /// .ok(event_id)
1891 /// .expect(1)
1892 /// .mount()
1893 /// .await;
1894 ///
1895 /// let content = RoomMessageEventContent::text_plain("Hello world");
1896 /// let response = room.send(content).await?;
1897 ///
1898 /// assert_eq!(
1899 /// event_id,
1900 /// response.event_id,
1901 /// "The event ID we mocked should match the one we received when we sent the event"
1902 /// );
1903 /// # anyhow::Ok(()) });
1904 /// ```
1905 pub fn body_matches_partial_json(self, body: Value) -> Self {
1906 Self { mock: self.mock.and(body_partial_json(body)), ..self }
1907 }
1908
1909 /// Ensures that the send endpoint request uses a specific event type.
1910 ///
1911 /// # Examples
1912 ///
1913 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1914 ///
1915 /// ```
1916 /// # tokio_test::block_on(async {
1917 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1918 /// use serde_json::json;
1919 ///
1920 /// let mock_server = MatrixMockServer::new().await;
1921 /// let client = mock_server.client_builder().build().await;
1922 ///
1923 /// mock_server.mock_room_state_encryption().plain().mount().await;
1924 ///
1925 /// let room = mock_server
1926 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1927 /// .await;
1928 ///
1929 /// let event_id = event_id!("$some_id");
1930 /// mock_server
1931 /// .mock_room_send()
1932 /// .for_type("m.room.message".into())
1933 /// .ok(event_id)
1934 /// .expect(1)
1935 /// .mount()
1936 /// .await;
1937 ///
1938 /// let response_not_mocked = room.send_raw("m.room.reaction", json!({ "body": "Hello world" })).await;
1939 /// // The `m.room.reaction` event type should not be mocked by the server.
1940 /// assert!(response_not_mocked.is_err());
1941 ///
1942 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
1943 /// // The `m.room.message` event type should be mocked by the server.
1944 /// assert_eq!(
1945 /// event_id,
1946 /// response.event_id,
1947 /// "The event ID we mocked should match the one we received when we sent the event"
1948 /// );
1949 /// # anyhow::Ok(()) });
1950 /// ```
1951 pub fn for_type(self, event_type: MessageLikeEventType) -> Self {
1952 Self {
1953 // Note: we already defined a path when constructing the mock builder, but this one
1954 // ought to be more specialized.
1955 mock: self
1956 .mock
1957 .and(path_regex(format!(r"^/_matrix/client/v3/rooms/.*/send/{event_type}",))),
1958 ..self
1959 }
1960 }
1961
1962 /// Ensures the event was sent as a delayed event.
1963 ///
1964 /// See also [the MSC](https://github.com/matrix-org/matrix-spec-proposals/pull/4140).
1965 ///
1966 /// Note: works with *any* room.
1967 ///
1968 /// # Examples
1969 ///
1970 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1971 ///
1972 /// ```
1973 /// # tokio_test::block_on(async {
1974 /// use matrix_sdk::{
1975 /// ruma::{
1976 /// api::client::delayed_events::{delayed_message_event, DelayParameters},
1977 /// events::{message::MessageEventContent, AnyMessageLikeEventContent},
1978 /// room_id,
1979 /// time::Duration,
1980 /// TransactionId,
1981 /// },
1982 /// test_utils::mocks::MatrixMockServer,
1983 /// };
1984 /// use serde_json::json;
1985 /// use wiremock::ResponseTemplate;
1986 ///
1987 /// let mock_server = MatrixMockServer::new().await;
1988 /// let client = mock_server.client_builder().build().await;
1989 ///
1990 /// mock_server.mock_room_state_encryption().plain().mount().await;
1991 ///
1992 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
1993 ///
1994 /// mock_server
1995 /// .mock_room_send()
1996 /// .match_delayed_event(Duration::from_millis(500))
1997 /// .respond_with(ResponseTemplate::new(200).set_body_json(json!({"delay_id":"$some_id"})))
1998 /// .mock_once()
1999 /// .mount()
2000 /// .await;
2001 ///
2002 /// let response_not_mocked =
2003 /// room.send_raw("m.room.message", json!({ "body": "Hello world" })).await;
2004 ///
2005 /// // A non delayed event should not be mocked by the server.
2006 /// assert!(response_not_mocked.is_err());
2007 ///
2008 /// let r = delayed_message_event::unstable::Request::new(
2009 /// room.room_id().to_owned(),
2010 /// TransactionId::new(),
2011 /// DelayParameters::Timeout { timeout: Duration::from_millis(500) },
2012 /// &AnyMessageLikeEventContent::Message(MessageEventContent::plain("hello world")),
2013 /// )
2014 /// .unwrap();
2015 ///
2016 /// let response = room.client().send(r).await.unwrap();
2017 /// // The delayed `m.room.message` event type should be mocked by the server.
2018 /// assert_eq!("$some_id", response.delay_id);
2019 /// # anyhow::Ok(()) });
2020 /// ```
2021 pub fn match_delayed_event(self, delay: Duration) -> Self {
2022 Self {
2023 mock: self
2024 .mock
2025 .and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
2026 ..self
2027 }
2028 }
2029
2030 /// Returns a send endpoint that emulates success, i.e. the event has been
2031 /// sent with the given event id.
2032 ///
2033 /// # Examples
2034 /// ```
2035 /// # tokio_test::block_on(async {
2036 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
2037 /// use serde_json::json;
2038 ///
2039 /// let mock_server = MatrixMockServer::new().await;
2040 /// let client = mock_server.client_builder().build().await;
2041 ///
2042 /// mock_server.mock_room_state_encryption().plain().mount().await;
2043 ///
2044 /// let room = mock_server
2045 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2046 /// .await;
2047 ///
2048 /// let event_id = event_id!("$some_id");
2049 /// let send_guard = mock_server
2050 /// .mock_room_send()
2051 /// .ok(event_id)
2052 /// .expect(1)
2053 /// .mount_as_scoped()
2054 /// .await;
2055 ///
2056 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
2057 ///
2058 /// assert_eq!(
2059 /// event_id,
2060 /// response.event_id,
2061 /// "The event ID we mocked should match the one we received when we sent the event"
2062 /// );
2063 /// # anyhow::Ok(()) });
2064 /// ```
2065 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
2066 self.ok_with_event_id(returned_event_id.into())
2067 }
2068
2069 /// Returns a send endpoint that emulates success, i.e. the event has been
2070 /// sent with the given event id.
2071 ///
2072 /// The sent event is captured and can be accessed using the returned
2073 /// [`Receiver`]. The [`Receiver`] is valid only for a send call. The given
2074 /// `event_sender` are added to the event JSON.
2075 ///
2076 /// # Examples
2077 ///
2078 /// ```no_run
2079 /// # tokio_test::block_on(async {
2080 /// use matrix_sdk::{
2081 /// ruma::{
2082 /// event_id, events::room::message::RoomMessageEventContent, room_id,
2083 /// },
2084 /// test_utils::mocks::MatrixMockServer,
2085 /// };
2086 /// use matrix_sdk_test::JoinedRoomBuilder;
2087 ///
2088 /// let room_id = room_id!("!room_id:localhost");
2089 /// let event_id = event_id!("$some_id");
2090 ///
2091 /// let server = MatrixMockServer::new().await;
2092 /// let client = server.client_builder().build().await;
2093 ///
2094 /// let user_id = client.user_id().expect("We should have a user ID by now");
2095 ///
2096 /// let (receiver, mock) =
2097 /// server.mock_room_send().ok_with_capture(event_id, user_id);
2098 ///
2099 /// server
2100 /// .mock_sync()
2101 /// .ok_and_run(&client, |builder| {
2102 /// builder.add_joined_room(JoinedRoomBuilder::new(room_id));
2103 /// })
2104 /// .await;
2105 ///
2106 /// // Mock any additional endpoints that might be needed to send the message.
2107 ///
2108 /// let room = client
2109 /// .get_room(room_id)
2110 /// .expect("We should have access to our room now");
2111 ///
2112 /// let event_id = room
2113 /// .send(RoomMessageEventContent::text_plain("It's a secret to everybody"))
2114 /// .await
2115 /// .expect("We should be able to send an initial message")
2116 /// .event_id;
2117 ///
2118 /// let event = receiver.await?;
2119 /// # anyhow::Ok(()) });
2120 /// ```
2121 pub fn ok_with_capture(
2122 self,
2123 returned_event_id: impl Into<OwnedEventId>,
2124 event_sender: impl Into<OwnedUserId>,
2125 ) -> (Receiver<Raw<AnySyncTimelineEvent>>, MatrixMock<'a>) {
2126 let event_id = returned_event_id.into();
2127 let event_sender = event_sender.into();
2128
2129 let (sender, receiver) = oneshot::channel();
2130 let sender = Arc::new(Mutex::new(Some(sender)));
2131
2132 let ret = self.respond_with(move |request: &Request| {
2133 if let Some(sender) = sender.lock().unwrap().take() {
2134 let uri = &request.url;
2135 let path_segments = uri.path_segments();
2136 let maybe_event_type = path_segments.and_then(|mut s| s.nth_back(1));
2137 let event_type = maybe_event_type
2138 .as_ref()
2139 .map(|&e| e.to_owned())
2140 .unwrap_or("m.room.message".to_owned());
2141
2142 let body: Value =
2143 request.body_json().expect("The received body should be valid JSON");
2144
2145 let event = json!({
2146 "event_id": event_id.clone(),
2147 "sender": event_sender,
2148 "type": event_type,
2149 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
2150 "content": body,
2151 });
2152
2153 let event: Raw<AnySyncTimelineEvent> = from_value(event)
2154 .expect("We should be able to create a raw event from the content");
2155
2156 sender.send(event).expect("We should be able to send the event to the receiver");
2157 }
2158
2159 ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id.clone() }))
2160 });
2161
2162 (receiver, ret)
2163 }
2164}
2165
2166/// A prebuilt mock for sending a state event in a room.
2167#[derive(Default)]
2168pub struct RoomSendStateEndpoint {
2169 state_key: Option<String>,
2170 event_type: Option<StateEventType>,
2171}
2172
2173impl<'a> MockEndpoint<'a, RoomSendStateEndpoint> {
2174 fn generate_path_regexp(endpoint: &RoomSendStateEndpoint) -> String {
2175 format!(
2176 r"^/_matrix/client/v3/rooms/.*/state/{}/{}",
2177 endpoint.event_type.as_ref().map_or_else(|| ".*".to_owned(), |t| t.to_string()),
2178 endpoint.state_key.as_ref().map_or_else(|| ".*".to_owned(), |k| k.to_string())
2179 )
2180 }
2181
2182 /// Ensures that the body of the request is a superset of the provided
2183 /// `body` parameter.
2184 ///
2185 /// # Examples
2186 /// ```
2187 /// # tokio_test::block_on(async {
2188 /// use matrix_sdk::{
2189 /// ruma::{
2190 /// room_id, event_id,
2191 /// events::room::power_levels::RoomPowerLevelsEventContent,
2192 /// room_version_rules::AuthorizationRules
2193 /// },
2194 /// test_utils::mocks::MatrixMockServer
2195 /// };
2196 /// use serde_json::json;
2197 ///
2198 /// let mock_server = MatrixMockServer::new().await;
2199 /// let client = mock_server.client_builder().build().await;
2200 ///
2201 /// mock_server.mock_room_state_encryption().plain().mount().await;
2202 ///
2203 /// let room = mock_server
2204 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2205 /// .await;
2206 ///
2207 /// let event_id = event_id!("$some_id");
2208 /// mock_server
2209 /// .mock_room_send_state()
2210 /// .body_matches_partial_json(json!({
2211 /// "redact": 51,
2212 /// }))
2213 /// .ok(event_id)
2214 /// .expect(1)
2215 /// .mount()
2216 /// .await;
2217 ///
2218 /// let mut content = RoomPowerLevelsEventContent::new(&AuthorizationRules::V1);
2219 /// // Update the power level to a non default value.
2220 /// // Otherwise it will be skipped from serialization.
2221 /// content.redact = 51.into();
2222 ///
2223 /// let response = room.send_state_event(content).await?;
2224 ///
2225 /// assert_eq!(
2226 /// event_id,
2227 /// response.event_id,
2228 /// "The event ID we mocked should match the one we received when we sent the event"
2229 /// );
2230 /// # anyhow::Ok(()) });
2231 /// ```
2232 pub fn body_matches_partial_json(self, body: Value) -> Self {
2233 Self { mock: self.mock.and(body_partial_json(body)), ..self }
2234 }
2235
2236 /// Ensures that the send endpoint request uses a specific event type.
2237 ///
2238 /// Note: works with *any* room.
2239 ///
2240 /// # Examples
2241 ///
2242 /// see also [`MatrixMockServer::mock_room_send`] for more context.
2243 ///
2244 /// ```
2245 /// # tokio_test::block_on(async {
2246 /// use matrix_sdk::{
2247 /// ruma::{
2248 /// event_id,
2249 /// events::room::{
2250 /// create::RoomCreateEventContent, power_levels::RoomPowerLevelsEventContent,
2251 /// },
2252 /// events::StateEventType,
2253 /// room_id,
2254 /// room_version_rules::AuthorizationRules,
2255 /// },
2256 /// test_utils::mocks::MatrixMockServer,
2257 /// };
2258 ///
2259 /// let mock_server = MatrixMockServer::new().await;
2260 /// let client = mock_server.client_builder().build().await;
2261 ///
2262 /// mock_server.mock_room_state_encryption().plain().mount().await;
2263 ///
2264 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
2265 ///
2266 /// let event_id = event_id!("$some_id");
2267 ///
2268 /// mock_server
2269 /// .mock_room_send_state()
2270 /// .for_type(StateEventType::RoomPowerLevels)
2271 /// .ok(event_id)
2272 /// .expect(1)
2273 /// .mount()
2274 /// .await;
2275 ///
2276 /// let response_not_mocked = room.send_state_event(RoomCreateEventContent::new_v11()).await;
2277 /// // The `m.room.reaction` event type should not be mocked by the server.
2278 /// assert!(response_not_mocked.is_err());
2279 ///
2280 /// let response = room.send_state_event(RoomPowerLevelsEventContent::new(&AuthorizationRules::V1)).await?;
2281 /// // The `m.room.message` event type should be mocked by the server.
2282 /// assert_eq!(
2283 /// event_id, response.event_id,
2284 /// "The event ID we mocked should match the one we received when we sent the event"
2285 /// );
2286 ///
2287 /// # anyhow::Ok(()) });
2288 /// ```
2289 pub fn for_type(mut self, event_type: StateEventType) -> Self {
2290 self.endpoint.event_type = Some(event_type);
2291 // Note: we may have already defined a path, but this one ought to be more
2292 // specialized (unless for_key/for_type were called multiple times).
2293 Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
2294 }
2295
2296 /// Ensures the event was sent as a delayed event.
2297 ///
2298 /// See also [the MSC](https://github.com/matrix-org/matrix-spec-proposals/pull/4140).
2299 ///
2300 /// Note: works with *any* room.
2301 ///
2302 /// # Examples
2303 ///
2304 /// see also [`MatrixMockServer::mock_room_send`] for more context.
2305 ///
2306 /// ```
2307 /// # tokio_test::block_on(async {
2308 /// use matrix_sdk::{
2309 /// ruma::{
2310 /// api::client::delayed_events::{delayed_state_event, DelayParameters},
2311 /// events::{room::create::RoomCreateEventContent, AnyStateEventContent},
2312 /// room_id,
2313 /// time::Duration,
2314 /// },
2315 /// test_utils::mocks::MatrixMockServer,
2316 /// };
2317 /// use wiremock::ResponseTemplate;
2318 /// use serde_json::json;
2319 ///
2320 /// let mock_server = MatrixMockServer::new().await;
2321 /// let client = mock_server.client_builder().build().await;
2322 ///
2323 /// mock_server.mock_room_state_encryption().plain().mount().await;
2324 ///
2325 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
2326 ///
2327 /// mock_server
2328 /// .mock_room_send_state()
2329 /// .match_delayed_event(Duration::from_millis(500))
2330 /// .respond_with(ResponseTemplate::new(200).set_body_json(json!({"delay_id":"$some_id"})))
2331 /// .mock_once()
2332 /// .mount()
2333 /// .await;
2334 ///
2335 /// let response_not_mocked = room.send_state_event(RoomCreateEventContent::new_v11()).await;
2336 /// // A non delayed event should not be mocked by the server.
2337 /// assert!(response_not_mocked.is_err());
2338 ///
2339 /// let r = delayed_state_event::unstable::Request::new(
2340 /// room.room_id().to_owned(),
2341 /// "".to_owned(),
2342 /// DelayParameters::Timeout { timeout: Duration::from_millis(500) },
2343 /// &AnyStateEventContent::RoomCreate(RoomCreateEventContent::new_v11()),
2344 /// )
2345 /// .unwrap();
2346 /// let response = room.client().send(r).await.unwrap();
2347 /// // The delayed `m.room.message` event type should be mocked by the server.
2348 /// assert_eq!("$some_id", response.delay_id);
2349 ///
2350 /// # anyhow::Ok(()) });
2351 /// ```
2352 pub fn match_delayed_event(self, delay: Duration) -> Self {
2353 Self {
2354 mock: self
2355 .mock
2356 .and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
2357 ..self
2358 }
2359 }
2360
2361 ///
2362 /// ```
2363 /// # tokio_test::block_on(async {
2364 /// use matrix_sdk::{
2365 /// ruma::{
2366 /// event_id,
2367 /// events::{call::member::CallMemberEventContent, AnyStateEventContent},
2368 /// room_id,
2369 /// },
2370 /// test_utils::mocks::MatrixMockServer,
2371 /// };
2372 ///
2373 /// let mock_server = MatrixMockServer::new().await;
2374 /// let client = mock_server.client_builder().build().await;
2375 ///
2376 /// mock_server.mock_room_state_encryption().plain().mount().await;
2377 ///
2378 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
2379 ///
2380 /// let event_id = event_id!("$some_id");
2381 ///
2382 /// mock_server
2383 /// .mock_room_send_state()
2384 /// .for_key("my_key".to_owned())
2385 /// .ok(event_id)
2386 /// .expect(1)
2387 /// .mount()
2388 /// .await;
2389 ///
2390 /// let response_not_mocked = room
2391 /// .send_state_event_for_key(
2392 /// "",
2393 /// AnyStateEventContent::CallMember(CallMemberEventContent::new_empty(None)),
2394 /// )
2395 /// .await;
2396 /// // The `m.room.reaction` event type should not be mocked by the server.
2397 /// assert!(response_not_mocked.is_err());
2398 ///
2399 /// let response = room
2400 /// .send_state_event_for_key(
2401 /// "my_key",
2402 /// AnyStateEventContent::CallMember(CallMemberEventContent::new_empty(None)),
2403 /// )
2404 /// .await
2405 /// .unwrap();
2406 ///
2407 /// // The `m.room.message` event type should be mocked by the server.
2408 /// assert_eq!(
2409 /// event_id, response.event_id,
2410 /// "The event ID we mocked should match the one we received when we sent the event"
2411 /// );
2412 /// # anyhow::Ok(()) });
2413 /// ```
2414 pub fn for_key(mut self, state_key: String) -> Self {
2415 self.endpoint.state_key = Some(state_key);
2416 // Note: we may have already defined a path, but this one ought to be more
2417 // specialized (unless for_key/for_type were called multiple times).
2418 Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
2419 }
2420
2421 /// Returns a send endpoint that emulates success, i.e. the event has been
2422 /// sent with the given event id.
2423 ///
2424 /// # Examples
2425 /// ```
2426 /// # tokio_test::block_on(async {
2427 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
2428 /// use serde_json::json;
2429 ///
2430 /// let mock_server = MatrixMockServer::new().await;
2431 /// let client = mock_server.client_builder().build().await;
2432 ///
2433 /// mock_server.mock_room_state_encryption().plain().mount().await;
2434 ///
2435 /// let room = mock_server
2436 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2437 /// .await;
2438 ///
2439 /// let event_id = event_id!("$some_id");
2440 /// let send_guard = mock_server
2441 /// .mock_room_send_state()
2442 /// .ok(event_id)
2443 /// .expect(1)
2444 /// .mount_as_scoped()
2445 /// .await;
2446 ///
2447 /// let response = room.send_state_event_raw("m.room.message", "my_key", json!({ "body": "Hello world" })).await?;
2448 ///
2449 /// assert_eq!(
2450 /// event_id,
2451 /// response.event_id,
2452 /// "The event ID we mocked should match the one we received when we sent the event"
2453 /// );
2454 /// # anyhow::Ok(()) });
2455 /// ```
2456 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
2457 self.ok_with_event_id(returned_event_id.into())
2458 }
2459}
2460
2461/// A prebuilt mock for running sync v2.
2462pub struct SyncEndpoint {
2463 sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
2464}
2465
2466impl<'a> MockEndpoint<'a, SyncEndpoint> {
2467 /// Expect the given timeout, or lack thereof, in the request.
2468 pub fn timeout(mut self, timeout: Option<Duration>) -> Self {
2469 if let Some(timeout) = timeout {
2470 self.mock = self.mock.and(query_param("timeout", timeout.as_millis().to_string()));
2471 } else {
2472 self.mock = self.mock.and(query_param_is_missing("timeout"));
2473 }
2474
2475 self
2476 }
2477
2478 /// Mocks the sync endpoint, using the given function to generate the
2479 /// response.
2480 pub fn ok<F: FnOnce(&mut SyncResponseBuilder)>(self, func: F) -> MatrixMock<'a> {
2481 let json_response = {
2482 let mut builder = self.endpoint.sync_response_builder.lock().unwrap();
2483 func(&mut builder);
2484 builder.build_json_sync_response()
2485 };
2486
2487 self.respond_with(ResponseTemplate::new(200).set_body_json(json_response))
2488 }
2489
2490 /// Temporarily mocks the sync with the given endpoint and runs a client
2491 /// sync with it.
2492 ///
2493 /// After calling this function, the sync endpoint isn't mocked anymore.
2494 ///
2495 /// # Examples
2496 ///
2497 /// ```
2498 /// # tokio_test::block_on(async {
2499 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
2500 /// use matrix_sdk_test::JoinedRoomBuilder;
2501 ///
2502 /// // First create the mock server and client pair.
2503 /// let mock_server = MatrixMockServer::new().await;
2504 /// let client = mock_server.client_builder().build().await;
2505 /// let room_id = room_id!("!room_id:localhost");
2506 ///
2507 /// // Let's emulate what `MatrixMockServer::sync_joined_room()` does.
2508 /// mock_server
2509 /// .mock_sync()
2510 /// .ok_and_run(&client, |builder| {
2511 /// builder.add_joined_room(JoinedRoomBuilder::new(room_id));
2512 /// })
2513 /// .await;
2514 ///
2515 /// let room = client
2516 /// .get_room(room_id)
2517 /// .expect("The room should be available after we mocked the sync");
2518 /// # anyhow::Ok(()) });
2519 /// ```
2520 pub async fn ok_and_run<F: FnOnce(&mut SyncResponseBuilder)>(self, client: &Client, func: F) {
2521 let _scope = self.ok(func).mount_as_scoped().await;
2522
2523 let _response = client.sync_once(Default::default()).await.unwrap();
2524 }
2525}
2526
2527/// A prebuilt mock for reading the encryption state of a room.
2528pub struct EncryptionStateEndpoint;
2529
2530impl<'a> MockEndpoint<'a, EncryptionStateEndpoint> {
2531 /// Marks the room as encrypted.
2532 ///
2533 /// # Examples
2534 ///
2535 /// ```
2536 /// # tokio_test::block_on(async {
2537 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
2538 ///
2539 /// let mock_server = MatrixMockServer::new().await;
2540 /// let client = mock_server.client_builder().build().await;
2541 ///
2542 /// mock_server.mock_room_state_encryption().encrypted().mount().await;
2543 ///
2544 /// let room = mock_server
2545 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2546 /// .await;
2547 ///
2548 /// assert!(
2549 /// room.latest_encryption_state().await?.is_encrypted(),
2550 /// "The room should be marked as encrypted."
2551 /// );
2552 /// # anyhow::Ok(()) });
2553 /// ```
2554 pub fn encrypted(self) -> MatrixMock<'a> {
2555 self.respond_with(
2556 ResponseTemplate::new(200).set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
2557 )
2558 }
2559
2560 /// Marks the room as encrypted, opting into experimental state event
2561 /// encryption.
2562 ///
2563 /// # Examples
2564 ///
2565 /// ```
2566 /// # tokio_test::block_on(async {
2567 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
2568 ///
2569 /// let mock_server = MatrixMockServer::new().await;
2570 /// let client = mock_server.client_builder().build().await;
2571 ///
2572 /// mock_server.mock_room_state_encryption().state_encrypted().mount().await;
2573 ///
2574 /// let room = mock_server
2575 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2576 /// .await;
2577 ///
2578 /// assert!(
2579 /// room.latest_encryption_state().await?.is_state_encrypted(),
2580 /// "The room should be marked as state encrypted."
2581 /// );
2582 /// # anyhow::Ok(()) });
2583 #[cfg(feature = "experimental-encrypted-state-events")]
2584 pub fn state_encrypted(self) -> MatrixMock<'a> {
2585 self.respond_with(ResponseTemplate::new(200).set_body_json(
2586 &*test_json::sync_events::ENCRYPTION_WITH_ENCRYPTED_STATE_EVENTS_CONTENT,
2587 ))
2588 }
2589
2590 /// Marks the room as not encrypted.
2591 ///
2592 /// # Examples
2593 ///
2594 /// ```
2595 /// # tokio_test::block_on(async {
2596 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
2597 ///
2598 /// let mock_server = MatrixMockServer::new().await;
2599 /// let client = mock_server.client_builder().build().await;
2600 ///
2601 /// mock_server.mock_room_state_encryption().plain().mount().await;
2602 ///
2603 /// let room = mock_server
2604 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
2605 /// .await;
2606 ///
2607 /// assert!(
2608 /// !room.latest_encryption_state().await?.is_encrypted(),
2609 /// "The room should not be marked as encrypted."
2610 /// );
2611 /// # anyhow::Ok(()) });
2612 /// ```
2613 pub fn plain(self) -> MatrixMock<'a> {
2614 self.respond_with(ResponseTemplate::new(404).set_body_json(&*test_json::NOT_FOUND))
2615 }
2616}
2617
2618/// A prebuilt mock for setting the encryption state of a room.
2619pub struct SetEncryptionStateEndpoint;
2620
2621impl<'a> MockEndpoint<'a, SetEncryptionStateEndpoint> {
2622 /// Returns a mock for a successful setting of the encryption state event.
2623 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
2624 self.ok_with_event_id(returned_event_id.into())
2625 }
2626}
2627
2628/// A prebuilt mock for redacting an event in a room.
2629pub struct RoomRedactEndpoint;
2630
2631impl<'a> MockEndpoint<'a, RoomRedactEndpoint> {
2632 /// Returns a redact endpoint that emulates success, i.e. the redaction
2633 /// event has been sent with the given event id.
2634 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
2635 self.ok_with_event_id(returned_event_id.into())
2636 }
2637}
2638
2639/// A prebuilt mock for getting a single event in a room.
2640pub struct RoomEventEndpoint {
2641 room: Option<OwnedRoomId>,
2642 match_event_id: bool,
2643}
2644
2645impl<'a> MockEndpoint<'a, RoomEventEndpoint> {
2646 /// Limits the scope of this mock to a specific room.
2647 pub fn room(mut self, room: impl Into<OwnedRoomId>) -> Self {
2648 self.endpoint.room = Some(room.into());
2649 self
2650 }
2651
2652 /// Whether the mock checks for the event id from the event.
2653 pub fn match_event_id(mut self) -> Self {
2654 self.endpoint.match_event_id = true;
2655 self
2656 }
2657
2658 /// Returns a redact endpoint that emulates success, i.e. the redaction
2659 /// event has been sent with the given event id.
2660 pub fn ok(self, event: TimelineEvent) -> MatrixMock<'a> {
2661 let event_path = if self.endpoint.match_event_id {
2662 let event_id = event.kind.event_id().expect("an event id is required");
2663 // The event id should begin with `$`, which would be taken as the end of the
2664 // regex so we need to escape it
2665 event_id.as_str().replace("$", "\\$")
2666 } else {
2667 // Event is at the end, so no need to add anything.
2668 "".to_owned()
2669 };
2670
2671 let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string());
2672
2673 let mock = self
2674 .mock
2675 .and(path_regex(format!(r"^/_matrix/client/v3/rooms/{room_path}/event/{event_path}")))
2676 .respond_with(ResponseTemplate::new(200).set_body_json(event.into_raw().json()));
2677 MatrixMock { server: self.server, mock }
2678 }
2679}
2680
2681/// A prebuilt mock for getting a single event with its context in a room.
2682pub struct RoomEventContextEndpoint {
2683 room: Option<OwnedRoomId>,
2684 match_event_id: bool,
2685}
2686
2687impl<'a> MockEndpoint<'a, RoomEventContextEndpoint> {
2688 /// Limits the scope of this mock to a specific room.
2689 pub fn room(mut self, room: impl Into<OwnedRoomId>) -> Self {
2690 self.endpoint.room = Some(room.into());
2691 self
2692 }
2693
2694 /// Whether the mock checks for the event id from the event.
2695 pub fn match_event_id(mut self) -> Self {
2696 self.endpoint.match_event_id = true;
2697 self
2698 }
2699
2700 /// Returns an endpoint that emulates success.
2701 pub fn ok(
2702 self,
2703 event: TimelineEvent,
2704 start: impl Into<String>,
2705 end: impl Into<String>,
2706 state_events: Vec<Raw<AnyStateEvent>>,
2707 ) -> MatrixMock<'a> {
2708 let event_path = if self.endpoint.match_event_id {
2709 let event_id = event.event_id().expect("an event id is required");
2710 // The event id should begin with `$`, which would be taken as the end of the
2711 // regex so we need to escape it
2712 event_id.as_str().replace("$", "\\$")
2713 } else {
2714 // Event is at the end, so no need to add anything.
2715 "".to_owned()
2716 };
2717
2718 let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string());
2719
2720 let mock = self
2721 .mock
2722 .and(path_regex(format!(r"^/_matrix/client/v3/rooms/{room_path}/context/{event_path}")))
2723 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2724 "event": event.into_raw().json(),
2725 "end": end.into(),
2726 "start": start.into(),
2727 "state": state_events
2728 })));
2729 MatrixMock { server: self.server, mock }
2730 }
2731}
2732
2733/// A prebuilt mock for the `/messages` endpoint.
2734pub struct RoomMessagesEndpoint;
2735
2736/// A prebuilt mock for getting a room messages in a room.
2737impl<'a> MockEndpoint<'a, RoomMessagesEndpoint> {
2738 /// Expects an optional limit to be set on the request.
2739 pub fn match_limit(self, limit: u32) -> Self {
2740 Self { mock: self.mock.and(query_param("limit", limit.to_string())), ..self }
2741 }
2742
2743 /// Expects an optional `from` to be set on the request.
2744 pub fn match_from(self, from: &str) -> Self {
2745 Self { mock: self.mock.and(query_param("from", from)), ..self }
2746 }
2747
2748 /// Returns a messages endpoint that emulates success, i.e. the messages
2749 /// provided as `response` could be retrieved.
2750 ///
2751 /// Note: pass `chunk` in the correct order: topological for forward
2752 /// pagination, reverse topological for backwards pagination.
2753 pub fn ok(self, response: RoomMessagesResponseTemplate) -> MatrixMock<'a> {
2754 let mut template = ResponseTemplate::new(200).set_body_json(json!({
2755 "start": response.start,
2756 "end": response.end,
2757 "chunk": response.chunk,
2758 "state": response.state,
2759 }));
2760
2761 if let Some(delay) = response.delay {
2762 template = template.set_delay(delay);
2763 }
2764
2765 self.respond_with(template)
2766 }
2767}
2768
2769/// A response to a [`RoomMessagesEndpoint`] query.
2770pub struct RoomMessagesResponseTemplate {
2771 /// The start token for this /messages query.
2772 pub start: String,
2773 /// The end token for this /messages query (previous batch for back
2774 /// paginations, next batch for forward paginations).
2775 pub end: Option<String>,
2776 /// The set of timeline events returned by this query.
2777 pub chunk: Vec<Raw<AnyTimelineEvent>>,
2778 /// The set of state events returned by this query.
2779 pub state: Vec<Raw<AnyStateEvent>>,
2780 /// Optional delay to respond to the query.
2781 pub delay: Option<Duration>,
2782}
2783
2784impl RoomMessagesResponseTemplate {
2785 /// Fill the events returned as part of this response.
2786 pub fn events(mut self, chunk: Vec<impl Into<Raw<AnyTimelineEvent>>>) -> Self {
2787 self.chunk = chunk.into_iter().map(Into::into).collect();
2788 self
2789 }
2790
2791 /// Fill the end token.
2792 pub fn end_token(mut self, token: impl Into<String>) -> Self {
2793 self.end = Some(token.into());
2794 self
2795 }
2796
2797 /// Respond with a given delay to the query.
2798 pub fn with_delay(mut self, delay: Duration) -> Self {
2799 self.delay = Some(delay);
2800 self
2801 }
2802}
2803
2804impl Default for RoomMessagesResponseTemplate {
2805 fn default() -> Self {
2806 Self {
2807 start: "start-token-unused".to_owned(),
2808 end: Default::default(),
2809 chunk: Default::default(),
2810 state: Default::default(),
2811 delay: None,
2812 }
2813 }
2814}
2815
2816/// A prebuilt mock for uploading media.
2817pub struct UploadEndpoint;
2818
2819impl<'a> MockEndpoint<'a, UploadEndpoint> {
2820 /// Expect that the content type matches what's given here.
2821 pub fn expect_mime_type(self, content_type: &str) -> Self {
2822 Self { mock: self.mock.and(header("content-type", content_type)), ..self }
2823 }
2824
2825 /// Returns a upload endpoint that emulates success, i.e. the media has been
2826 /// uploaded to the media server and can be accessed using the given
2827 /// event has been sent with the given [`MxcUri`].
2828 ///
2829 /// The uploaded content is captured and can be accessed using the returned
2830 /// [`Receiver`]. The [`Receiver`] is valid only for a single media
2831 /// upload.
2832 ///
2833 /// # Examples
2834 ///
2835 /// ```no_run
2836 /// # tokio_test::block_on(async {
2837 /// use matrix_sdk::{
2838 /// ruma::{event_id, mxc_uri, room_id},
2839 /// test_utils::mocks::MatrixMockServer,
2840 /// };
2841 ///
2842 /// let mxid = mxc_uri!("mxc://localhost/12345");
2843 ///
2844 /// let server = MatrixMockServer::new().await;
2845 /// let (receiver, upload_mock) = server.mock_upload().ok_with_capture(mxid);
2846 /// let client = server.client_builder().build().await;
2847 ///
2848 /// client.media().upload(&mime::TEXT_PLAIN, vec![1, 2, 3, 4, 5], None).await?;
2849 ///
2850 /// let uploaded = receiver.await?;
2851 ///
2852 /// assert_eq!(uploaded, vec![1, 2, 3, 4, 5]);
2853 /// # anyhow::Ok(()) });
2854 /// ```
2855 pub fn ok_with_capture(self, mxc_id: &MxcUri) -> (Receiver<Vec<u8>>, MatrixMock<'a>) {
2856 let (sender, receiver) = oneshot::channel();
2857 let sender = Arc::new(Mutex::new(Some(sender)));
2858 let response_body = json!({"content_uri": mxc_id});
2859
2860 let ret = self.respond_with(move |request: &Request| {
2861 let maybe_sender = sender.lock().unwrap().take();
2862
2863 if let Some(sender) = maybe_sender {
2864 let body = request.body.clone();
2865 let _ = sender.send(body);
2866 }
2867
2868 ResponseTemplate::new(200).set_body_json(response_body.clone())
2869 });
2870
2871 (receiver, ret)
2872 }
2873
2874 /// Returns a upload endpoint that emulates success, i.e. the media has been
2875 /// uploaded to the media server and can be accessed using the given
2876 /// event has been sent with the given [`MxcUri`].
2877 pub fn ok(self, mxc_id: &MxcUri) -> MatrixMock<'a> {
2878 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2879 "content_uri": mxc_id
2880 })))
2881 }
2882}
2883
2884/// A prebuilt mock for resolving a room alias.
2885pub struct ResolveRoomAliasEndpoint;
2886
2887impl<'a> MockEndpoint<'a, ResolveRoomAliasEndpoint> {
2888 /// Sets up the endpoint to only intercept requests for the given room
2889 /// alias.
2890 pub fn for_alias(self, alias: impl Into<String>) -> Self {
2891 let alias = alias.into();
2892 Self {
2893 mock: self.mock.and(path_regex(format!(
2894 r"^/_matrix/client/v3/directory/room/{}",
2895 percent_encoded_path(&alias)
2896 ))),
2897 ..self
2898 }
2899 }
2900
2901 /// Returns a data endpoint with a resolved room alias.
2902 pub fn ok(self, room_id: &str, servers: Vec<String>) -> MatrixMock<'a> {
2903 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2904 "room_id": room_id,
2905 "servers": servers,
2906 })))
2907 }
2908
2909 /// Returns a data endpoint for a room alias that does not exit.
2910 pub fn not_found(self) -> MatrixMock<'a> {
2911 self.respond_with(ResponseTemplate::new(404).set_body_json(json!({
2912 "errcode": "M_NOT_FOUND",
2913 "error": "Room alias not found."
2914 })))
2915 }
2916}
2917
2918/// A prebuilt mock for creating a room alias.
2919pub struct CreateRoomAliasEndpoint;
2920
2921impl<'a> MockEndpoint<'a, CreateRoomAliasEndpoint> {
2922 /// Returns a data endpoint for creating a room alias.
2923 pub fn ok(self) -> MatrixMock<'a> {
2924 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
2925 }
2926}
2927
2928/// A prebuilt mock for removing a room alias.
2929pub struct RemoveRoomAliasEndpoint;
2930
2931impl<'a> MockEndpoint<'a, RemoveRoomAliasEndpoint> {
2932 /// Returns a data endpoint for removing a room alias.
2933 pub fn ok(self) -> MatrixMock<'a> {
2934 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
2935 }
2936}
2937
2938/// A prebuilt mock for paginating the public room list.
2939pub struct PublicRoomsEndpoint;
2940
2941impl<'a> MockEndpoint<'a, PublicRoomsEndpoint> {
2942 /// Returns a data endpoint for paginating the public room list.
2943 pub fn ok(
2944 self,
2945 chunk: Vec<PublicRoomsChunk>,
2946 next_batch: Option<String>,
2947 prev_batch: Option<String>,
2948 total_room_count_estimate: Option<u64>,
2949 ) -> MatrixMock<'a> {
2950 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2951 "chunk": chunk,
2952 "next_batch": next_batch,
2953 "prev_batch": prev_batch,
2954 "total_room_count_estimate": total_room_count_estimate,
2955 })))
2956 }
2957
2958 /// Returns a data endpoint for paginating the public room list with several
2959 /// `via` params.
2960 ///
2961 /// Each `via` param must be in the `server_map` parameter, otherwise it'll
2962 /// fail.
2963 pub fn ok_with_via_params(
2964 self,
2965 server_map: BTreeMap<OwnedServerName, Vec<PublicRoomsChunk>>,
2966 ) -> MatrixMock<'a> {
2967 self.respond_with(move |req: &Request| {
2968 #[derive(Deserialize)]
2969 struct PartialRequest {
2970 server: Option<OwnedServerName>,
2971 }
2972
2973 let (_, server) = req
2974 .url
2975 .query_pairs()
2976 .into_iter()
2977 .find(|(key, _)| key == "server")
2978 .expect("Server param not found in request URL");
2979 let server = ServerName::parse(server).expect("Couldn't parse server name");
2980 let chunk = server_map.get(&server).expect("Chunk for the server param not found");
2981 ResponseTemplate::new(200).set_body_json(json!({
2982 "chunk": chunk,
2983 "total_room_count_estimate": chunk.len(),
2984 }))
2985 })
2986 }
2987}
2988
2989/// A prebuilt mock for getting the room's visibility in the room directory.
2990pub struct GetRoomVisibilityEndpoint;
2991
2992impl<'a> MockEndpoint<'a, GetRoomVisibilityEndpoint> {
2993 /// Returns an endpoint that get the room's public visibility.
2994 pub fn ok(self, visibility: Visibility) -> MatrixMock<'a> {
2995 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2996 "visibility": visibility,
2997 })))
2998 }
2999}
3000
3001/// A prebuilt mock for setting the room's visibility in the room directory.
3002pub struct SetRoomVisibilityEndpoint;
3003
3004impl<'a> MockEndpoint<'a, SetRoomVisibilityEndpoint> {
3005 /// Returns an endpoint that updates the room's visibility.
3006 pub fn ok(self) -> MatrixMock<'a> {
3007 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3008 }
3009}
3010
3011/// A prebuilt mock for `GET room_keys/version`: storage ("backup") of room
3012/// keys.
3013pub struct RoomKeysVersionEndpoint;
3014
3015impl<'a> MockEndpoint<'a, RoomKeysVersionEndpoint> {
3016 /// Returns an endpoint that says there is a single room keys backup
3017 pub fn exists(self) -> MatrixMock<'a> {
3018 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3019 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
3020 "auth_data": {
3021 "public_key": "abcdefg",
3022 "signatures": {},
3023 },
3024 "count": 42,
3025 "etag": "anopaquestring",
3026 "version": "1",
3027 })))
3028 }
3029
3030 /// Returns an endpoint that says there is no room keys backup
3031 pub fn none(self) -> MatrixMock<'a> {
3032 self.respond_with(ResponseTemplate::new(404).set_body_json(json!({
3033 "errcode": "M_NOT_FOUND",
3034 "error": "No current backup version"
3035 })))
3036 }
3037
3038 /// Returns an endpoint that 429 errors when we get it
3039 pub fn error429(self) -> MatrixMock<'a> {
3040 self.respond_with(ResponseTemplate::new(429).set_body_json(json!({
3041 "errcode": "M_LIMIT_EXCEEDED",
3042 "error": "Too many requests",
3043 "retry_after_ms": 2000
3044 })))
3045 }
3046
3047 /// Returns an endpoint that 404 errors when we get it
3048 pub fn error404(self) -> MatrixMock<'a> {
3049 self.respond_with(ResponseTemplate::new(404))
3050 }
3051}
3052
3053/// A prebuilt mock for `POST room_keys/version`: adding room key backups.
3054pub struct AddRoomKeysVersionEndpoint;
3055
3056impl<'a> MockEndpoint<'a, AddRoomKeysVersionEndpoint> {
3057 /// Returns an endpoint that may be used to add room key backups
3058 pub fn ok(self) -> MatrixMock<'a> {
3059 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3060 "version": "1"
3061 })))
3062 .named("POST for the backup creation")
3063 }
3064}
3065
3066/// A prebuilt mock for `DELETE room_keys/version/xxx`: deleting room key
3067/// backups.
3068pub struct DeleteRoomKeysVersionEndpoint;
3069
3070impl<'a> MockEndpoint<'a, DeleteRoomKeysVersionEndpoint> {
3071 /// Returns an endpoint that allows deleting room key backups
3072 pub fn ok(self) -> MatrixMock<'a> {
3073 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3074 .named("DELETE for the backup deletion")
3075 }
3076}
3077
3078/// A prebuilt mock for the `/sendToDevice` endpoint.
3079///
3080/// This mock can be used to simulate sending to-device messages in tests.
3081pub struct SendToDeviceEndpoint;
3082impl<'a> MockEndpoint<'a, SendToDeviceEndpoint> {
3083 /// Returns a successful response with default data.
3084 pub fn ok(self) -> MatrixMock<'a> {
3085 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3086 }
3087}
3088
3089/// A prebuilt mock for `GET /members` request.
3090pub struct GetRoomMembersEndpoint;
3091
3092impl<'a> MockEndpoint<'a, GetRoomMembersEndpoint> {
3093 /// Returns a successful get members request with a list of members.
3094 pub fn ok(self, members: Vec<Raw<RoomMemberEvent>>) -> MatrixMock<'a> {
3095 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3096 "chunk": members,
3097 })))
3098 }
3099}
3100
3101/// A prebuilt mock for `POST /invite` request.
3102pub struct InviteUserByIdEndpoint;
3103
3104impl<'a> MockEndpoint<'a, InviteUserByIdEndpoint> {
3105 /// Returns a successful invite user by id request.
3106 pub fn ok(self) -> MatrixMock<'a> {
3107 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3108 }
3109}
3110
3111/// A prebuilt mock for `POST /kick` request.
3112pub struct KickUserEndpoint;
3113
3114impl<'a> MockEndpoint<'a, KickUserEndpoint> {
3115 /// Returns a successful kick user request.
3116 pub fn ok(self) -> MatrixMock<'a> {
3117 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3118 }
3119}
3120
3121/// A prebuilt mock for `POST /ban` request.
3122pub struct BanUserEndpoint;
3123
3124impl<'a> MockEndpoint<'a, BanUserEndpoint> {
3125 /// Returns a successful ban user request.
3126 pub fn ok(self) -> MatrixMock<'a> {
3127 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3128 }
3129}
3130
3131/// A prebuilt mock for `GET /versions` request.
3132pub struct VersionsEndpoint;
3133
3134impl<'a> MockEndpoint<'a, VersionsEndpoint> {
3135 // Get a JSON array of commonly supported versions.
3136 fn versions() -> Value {
3137 json!([
3138 "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",
3139 "v1.3", "v1.4", "v1.5", "v1.6", "v1.7", "v1.8", "v1.9", "v1.10", "v1.11"
3140 ])
3141 }
3142
3143 /// Returns a successful `/_matrix/client/versions` request.
3144 ///
3145 /// The response will return some commonly supported versions.
3146 pub fn ok(self) -> MatrixMock<'a> {
3147 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3148 "unstable_features": {},
3149 "versions": Self::versions()
3150 })))
3151 }
3152
3153 /// Returns a successful `/_matrix/client/versions` request.
3154 ///
3155 /// The response will return some commonly supported versions and unstable
3156 /// features supported by the SDK.
3157 pub fn ok_with_unstable_features(self) -> MatrixMock<'a> {
3158 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3159 "unstable_features": {
3160 "org.matrix.label_based_filtering": true,
3161 "org.matrix.e2e_cross_signing": true,
3162 "org.matrix.msc4028": true,
3163 "org.matrix.simplified_msc3575": true,
3164 },
3165 "versions": Self::versions()
3166 })))
3167 }
3168
3169 /// Returns a successful `/_matrix/client/versions` request with the given
3170 /// versions and unstable features.
3171 pub fn ok_custom(
3172 self,
3173 versions: &[&str],
3174 unstable_features: &BTreeMap<&str, bool>,
3175 ) -> MatrixMock<'a> {
3176 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3177 "unstable_features": unstable_features,
3178 "versions": versions,
3179 })))
3180 }
3181}
3182
3183/// A prebuilt mock for the room summary endpoint.
3184pub struct RoomSummaryEndpoint;
3185
3186impl<'a> MockEndpoint<'a, RoomSummaryEndpoint> {
3187 /// Returns a successful response with some default data for the given room
3188 /// id.
3189 pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
3190 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3191 "room_id": room_id,
3192 "guest_can_join": true,
3193 "num_joined_members": 1,
3194 "world_readable": true,
3195 "join_rule": "public",
3196 })))
3197 }
3198}
3199
3200/// A prebuilt mock to set a room's pinned events.
3201pub struct SetRoomPinnedEventsEndpoint;
3202
3203impl<'a> MockEndpoint<'a, SetRoomPinnedEventsEndpoint> {
3204 /// Returns a successful response with a given event id.
3205 /// id.
3206 pub fn ok(self, event_id: OwnedEventId) -> MatrixMock<'a> {
3207 self.ok_with_event_id(event_id)
3208 }
3209
3210 /// Returns an error response with a generic error code indicating the
3211 /// client is not authorized to set pinned events.
3212 pub fn unauthorized(self) -> MatrixMock<'a> {
3213 self.respond_with(ResponseTemplate::new(400))
3214 }
3215}
3216
3217/// A prebuilt mock for `GET /account/whoami` request.
3218pub struct WhoAmIEndpoint;
3219
3220impl<'a> MockEndpoint<'a, WhoAmIEndpoint> {
3221 /// Returns a successful response with the default device ID.
3222 pub fn ok(self) -> MatrixMock<'a> {
3223 self.ok_with_device_id(device_id!("D3V1C31D"))
3224 }
3225
3226 /// Returns a successful response with the given device ID.
3227 pub fn ok_with_device_id(self, device_id: &DeviceId) -> MatrixMock<'a> {
3228 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3229 "user_id": "@joe:example.org",
3230 "device_id": device_id,
3231 })))
3232 }
3233
3234 /// Returns an error response with an `M_UNKNOWN_TOKEN`.
3235 pub fn err_unknown_token(self) -> MatrixMock<'a> {
3236 self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
3237 "errcode": "M_UNKNOWN_TOKEN",
3238 "error": "Invalid token"
3239 })))
3240 }
3241}
3242
3243/// A prebuilt mock for `POST /keys/upload` request.
3244pub struct UploadKeysEndpoint;
3245
3246impl<'a> MockEndpoint<'a, UploadKeysEndpoint> {
3247 /// Returns a successful response with counts of 10 curve25519 keys and 20
3248 /// signed curve25519 keys.
3249 pub fn ok(self) -> MatrixMock<'a> {
3250 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3251 "one_time_key_counts": {
3252 "curve25519": 10,
3253 "signed_curve25519": 20,
3254 },
3255 })))
3256 }
3257}
3258
3259/// A prebuilt mock for `POST /keys/query` request.
3260pub struct QueryKeysEndpoint;
3261
3262impl<'a> MockEndpoint<'a, QueryKeysEndpoint> {
3263 /// Returns a successful empty response.
3264 pub fn ok(self) -> MatrixMock<'a> {
3265 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3266 }
3267}
3268
3269/// A prebuilt mock for `GET /.well-known/matrix/client` request.
3270pub struct WellKnownEndpoint;
3271
3272impl<'a> MockEndpoint<'a, WellKnownEndpoint> {
3273 /// Returns a successful response with the URL for this homeserver.
3274 pub fn ok(self) -> MatrixMock<'a> {
3275 let server_uri = self.server.uri();
3276 self.ok_with_homeserver_url(&server_uri)
3277 }
3278
3279 /// Returns a successful response with the given homeserver URL.
3280 pub fn ok_with_homeserver_url(self, homeserver_url: &str) -> MatrixMock<'a> {
3281 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3282 "m.homeserver": {
3283 "base_url": homeserver_url,
3284 },
3285 "m.rtc_foci": [
3286 {
3287 "type": "livekit",
3288 "livekit_service_url": "https://livekit.example.com",
3289 },
3290 ],
3291 })))
3292 }
3293
3294 /// Returns a 404 error response.
3295 pub fn error404(self) -> MatrixMock<'a> {
3296 self.respond_with(ResponseTemplate::new(404))
3297 }
3298}
3299
3300/// A prebuilt mock for `POST /keys/device_signing/upload` request.
3301pub struct UploadCrossSigningKeysEndpoint;
3302
3303impl<'a> MockEndpoint<'a, UploadCrossSigningKeysEndpoint> {
3304 /// Returns a successful empty response.
3305 pub fn ok(self) -> MatrixMock<'a> {
3306 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3307 }
3308
3309 /// Returns an error response with a UIAA stage that failed to authenticate
3310 /// because of an invalid password.
3311 pub fn uiaa_invalid_password(self) -> MatrixMock<'a> {
3312 self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
3313 "errcode": "M_FORBIDDEN",
3314 "error": "Invalid password",
3315 "flows": [
3316 {
3317 "stages": [
3318 "m.login.password"
3319 ]
3320 }
3321 ],
3322 "params": {},
3323 "session": "oFIJVvtEOCKmRUTYKTYIIPHL"
3324 })))
3325 }
3326
3327 /// Returns an error response with a UIAA stage.
3328 pub fn uiaa(self) -> MatrixMock<'a> {
3329 self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
3330 "flows": [
3331 {
3332 "stages": [
3333 "m.login.password"
3334 ]
3335 }
3336 ],
3337 "params": {},
3338 "session": "oFIJVvtEOCKmRUTYKTYIIPHL"
3339 })))
3340 }
3341
3342 /// Returns an error response with an OAuth 2.0 UIAA stage.
3343 pub fn uiaa_oauth(self) -> MatrixMock<'a> {
3344 let server_uri = self.server.uri();
3345 self.respond_with(ResponseTemplate::new(401).set_body_json(json!({
3346 "session": "dummy",
3347 "flows": [{
3348 "stages": [ "org.matrix.cross_signing_reset" ]
3349 }],
3350 "params": {
3351 "org.matrix.cross_signing_reset": {
3352 "url": format!("{server_uri}/account/?action=org.matrix.cross_signing_reset"),
3353 }
3354 },
3355 "msg": "To reset your end-to-end encryption cross-signing identity, you first need to approve it and then try again."
3356 })))
3357 }
3358}
3359
3360/// A prebuilt mock for `POST /keys/signatures/upload` request.
3361pub struct UploadCrossSigningSignaturesEndpoint;
3362
3363impl<'a> MockEndpoint<'a, UploadCrossSigningSignaturesEndpoint> {
3364 /// Returns a successful empty response.
3365 pub fn ok(self) -> MatrixMock<'a> {
3366 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3367 }
3368}
3369
3370/// A prebuilt mock for the room leave endpoint.
3371pub struct RoomLeaveEndpoint;
3372
3373impl<'a> MockEndpoint<'a, RoomLeaveEndpoint> {
3374 /// Returns a successful response with some default data for the given room
3375 /// id.
3376 pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
3377 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3378 "room_id": room_id,
3379 })))
3380 }
3381
3382 /// Returns a `M_FORBIDDEN` response.
3383 pub fn forbidden(self) -> MatrixMock<'a> {
3384 self.respond_with(ResponseTemplate::new(403).set_body_json(json!({
3385 "errcode": "M_FORBIDDEN",
3386 "error": "sowwy",
3387 })))
3388 }
3389}
3390
3391/// A prebuilt mock for the room forget endpoint.
3392pub struct RoomForgetEndpoint;
3393
3394impl<'a> MockEndpoint<'a, RoomForgetEndpoint> {
3395 /// Returns a successful response with some default data for the given room
3396 /// id.
3397 pub fn ok(self) -> MatrixMock<'a> {
3398 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3399 }
3400}
3401
3402/// A prebuilt mock for `POST /logout` request.
3403pub struct LogoutEndpoint;
3404
3405impl<'a> MockEndpoint<'a, LogoutEndpoint> {
3406 /// Returns a successful empty response.
3407 pub fn ok(self) -> MatrixMock<'a> {
3408 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3409 }
3410}
3411
3412/// A prebuilt mock for a `GET /rooms/{roomId}/threads` request.
3413pub struct RoomThreadsEndpoint;
3414
3415impl<'a> MockEndpoint<'a, RoomThreadsEndpoint> {
3416 /// Expects an optional `from` to be set on the request.
3417 pub fn match_from(self, from: &str) -> Self {
3418 Self { mock: self.mock.and(query_param("from", from)), ..self }
3419 }
3420
3421 /// Returns a successful response with some optional events and previous
3422 /// batch token.
3423 pub fn ok(
3424 self,
3425 chunk: Vec<Raw<AnyTimelineEvent>>,
3426 next_batch: Option<String>,
3427 ) -> MatrixMock<'a> {
3428 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3429 "chunk": chunk,
3430 "next_batch": next_batch
3431 })))
3432 }
3433}
3434
3435/// A prebuilt mock for a `GET /rooms/{roomId}/relations/{eventId}` family of
3436/// requests.
3437#[derive(Default)]
3438pub struct RoomRelationsEndpoint {
3439 event_id: Option<OwnedEventId>,
3440 spec: Option<IncludeRelations>,
3441}
3442
3443impl<'a> MockEndpoint<'a, RoomRelationsEndpoint> {
3444 /// Expects an optional `from` to be set on the request.
3445 pub fn match_from(self, from: &str) -> Self {
3446 Self { mock: self.mock.and(query_param("from", from)), ..self }
3447 }
3448
3449 /// Expects an optional `limit` to be set on the request.
3450 pub fn match_limit(self, limit: u32) -> Self {
3451 Self { mock: self.mock.and(query_param("limit", limit.to_string())), ..self }
3452 }
3453
3454 /// Match the given subrequest, according to the given specification.
3455 pub fn match_subrequest(mut self, spec: IncludeRelations) -> Self {
3456 self.endpoint.spec = Some(spec);
3457 self
3458 }
3459
3460 /// Expects the request to match a specific event id.
3461 pub fn match_target_event(mut self, event_id: OwnedEventId) -> Self {
3462 self.endpoint.event_id = Some(event_id);
3463 self
3464 }
3465
3466 /// Returns a successful response with some optional events and pagination
3467 /// tokens.
3468 pub fn ok(mut self, response: RoomRelationsResponseTemplate) -> MatrixMock<'a> {
3469 // Escape the leading $ to not confuse the regular expression engine.
3470 let event_spec = self
3471 .endpoint
3472 .event_id
3473 .take()
3474 .map(|event_id| event_id.as_str().replace("$", "\\$"))
3475 .unwrap_or_else(|| ".*".to_owned());
3476
3477 match self.endpoint.spec.take() {
3478 Some(IncludeRelations::RelationsOfType(rel_type)) => {
3479 self.mock = self.mock.and(path_regex(format!(
3480 r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}/{rel_type}$"
3481 )));
3482 }
3483 Some(IncludeRelations::RelationsOfTypeAndEventType(rel_type, event_type)) => {
3484 self.mock = self.mock.and(path_regex(format!(
3485 r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}/{rel_type}/{event_type}$"
3486 )));
3487 }
3488 _ => {
3489 self.mock = self.mock.and(path_regex(format!(
3490 r"^/_matrix/client/v1/rooms/.*/relations/{event_spec}",
3491 )));
3492 }
3493 }
3494
3495 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3496 "chunk": response.chunk,
3497 "next_batch": response.next_batch,
3498 "prev_batch": response.prev_batch,
3499 "recursion_depth": response.recursion_depth,
3500 })))
3501 }
3502}
3503
3504/// Helper function to set up a [`MockBuilder`] so it intercepts the account
3505/// data URLs.
3506fn global_account_data_mock_builder(
3507 builder: MockBuilder,
3508 user_id: &UserId,
3509 event_type: GlobalAccountDataEventType,
3510) -> MockBuilder {
3511 builder
3512 .and(path_regex(format!(r"^/_matrix/client/v3/user/{user_id}/account_data/{event_type}",)))
3513}
3514
3515/// A prebuilt mock for a `GET
3516/// /_matrix/client/v3/user/{userId}/account_data/io.element.recent_emoji`
3517/// request, which fetches the recently used emojis in the account data.
3518#[cfg(feature = "experimental-element-recent-emojis")]
3519pub struct GetRecentEmojisEndpoint;
3520
3521#[cfg(feature = "experimental-element-recent-emojis")]
3522impl<'a> MockEndpoint<'a, GetRecentEmojisEndpoint> {
3523 /// Returns a mock for a successful fetch of the recently used emojis in the
3524 /// account data.
3525 pub fn ok(self, user_id: &UserId, emojis: Vec<(String, UInt)>) -> MatrixMock<'a> {
3526 let mock =
3527 global_account_data_mock_builder(self.mock, user_id, "io.element.recent_emoji".into())
3528 .respond_with(
3529 ResponseTemplate::new(200).set_body_json(json!({ "recent_emoji": emojis })),
3530 );
3531 MatrixMock { server: self.server, mock }
3532 }
3533}
3534
3535/// A prebuilt mock for a `PUT
3536/// /_matrix/client/v3/user/{userId}/account_data/io.element.recent_emoji`
3537/// request, which updates the recently used emojis in the account data.
3538#[cfg(feature = "experimental-element-recent-emojis")]
3539pub struct UpdateRecentEmojisEndpoint {
3540 pub(crate) request_body: Option<Vec<(String, UInt)>>,
3541}
3542
3543#[cfg(feature = "experimental-element-recent-emojis")]
3544impl UpdateRecentEmojisEndpoint {
3545 /// Creates a new instance of the recent update recent emojis mock endpoint.
3546 fn new() -> Self {
3547 Self { request_body: None }
3548 }
3549}
3550
3551#[cfg(feature = "experimental-element-recent-emojis")]
3552impl<'a> MockEndpoint<'a, UpdateRecentEmojisEndpoint> {
3553 /// Returns a mock that will check the body of the request, making sure its
3554 /// contents match the provided list of emojis.
3555 pub fn match_emojis_in_request_body(self, emojis: Vec<(String, UInt)>) -> Self {
3556 Self::new(
3557 self.server,
3558 self.mock.and(body_json(json!(RecentEmojisContent::new(emojis)))),
3559 self.endpoint,
3560 )
3561 }
3562
3563 /// Returns a mock for a successful update of the recent emojis account data
3564 /// event. The request body contents should match the provided emoji
3565 /// list.
3566 #[cfg(feature = "experimental-element-recent-emojis")]
3567 pub fn ok(self, user_id: &UserId) -> MatrixMock<'a> {
3568 let mock =
3569 global_account_data_mock_builder(self.mock, user_id, "io.element.recent_emoji".into())
3570 .respond_with(ResponseTemplate::new(200).set_body_json(()));
3571 MatrixMock { server: self.server, mock }
3572 }
3573}
3574
3575/// A response to a [`RoomRelationsEndpoint`] query.
3576#[derive(Default)]
3577pub struct RoomRelationsResponseTemplate {
3578 /// The set of timeline events returned by this query.
3579 pub chunk: Vec<Raw<AnyTimelineEvent>>,
3580
3581 /// An opaque string representing a pagination token, which semantics depend
3582 /// on the direction used in the request.
3583 pub next_batch: Option<String>,
3584
3585 /// An opaque string representing a pagination token, which semantics depend
3586 /// on the direction used in the request.
3587 pub prev_batch: Option<String>,
3588
3589 /// If `recurse` was set on the request, the depth to which the server
3590 /// recursed.
3591 ///
3592 /// If `recurse` was not set, this field must be absent.
3593 pub recursion_depth: Option<u32>,
3594}
3595
3596impl RoomRelationsResponseTemplate {
3597 /// Fill the events returned as part of this response.
3598 pub fn events(mut self, chunk: Vec<impl Into<Raw<AnyTimelineEvent>>>) -> Self {
3599 self.chunk = chunk.into_iter().map(Into::into).collect();
3600 self
3601 }
3602
3603 /// Fill the `next_batch` token returned as part of this response.
3604 pub fn next_batch(mut self, token: impl Into<String>) -> Self {
3605 self.next_batch = Some(token.into());
3606 self
3607 }
3608
3609 /// Fill the `prev_batch` token returned as part of this response.
3610 pub fn prev_batch(mut self, token: impl Into<String>) -> Self {
3611 self.prev_batch = Some(token.into());
3612 self
3613 }
3614
3615 /// Fill the recursion depth returned in this response.
3616 pub fn recursion_depth(mut self, depth: u32) -> Self {
3617 self.recursion_depth = Some(depth);
3618 self
3619 }
3620}
3621
3622/// A prebuilt mock for `POST /rooms/{roomId}/receipt/{receiptType}/{eventId}`
3623/// request.
3624pub struct ReceiptEndpoint;
3625
3626impl<'a> MockEndpoint<'a, ReceiptEndpoint> {
3627 /// Returns a successful empty response.
3628 pub fn ok(self) -> MatrixMock<'a> {
3629 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3630 }
3631
3632 /// Ensures that the body of the request is a superset of the provided
3633 /// `body` parameter.
3634 pub fn body_matches_partial_json(self, body: Value) -> Self {
3635 Self { mock: self.mock.and(body_partial_json(body)), ..self }
3636 }
3637
3638 /// Ensures that the body of the request is the exact provided `body`
3639 /// parameter.
3640 pub fn body_json(self, body: Value) -> Self {
3641 Self { mock: self.mock.and(body_json(body)), ..self }
3642 }
3643
3644 /// Ensures that the request matches a specific receipt thread.
3645 pub fn match_thread(self, thread: ReceiptThread) -> Self {
3646 if let Some(thread_str) = thread.as_str() {
3647 self.body_matches_partial_json(json!({
3648 "thread_id": thread_str
3649 }))
3650 } else {
3651 self
3652 }
3653 }
3654
3655 /// Ensures that the request matches a specific event id.
3656 pub fn match_event_id(self, event_id: &EventId) -> Self {
3657 Self {
3658 mock: self.mock.and(path_regex(format!(
3659 r"^/_matrix/client/v3/rooms/.*/receipt/.*/{}$",
3660 event_id.as_str().replace("$", "\\$")
3661 ))),
3662 ..self
3663 }
3664 }
3665}
3666
3667/// A prebuilt mock for `POST /rooms/{roomId}/read_markers` request.
3668pub struct ReadMarkersEndpoint;
3669
3670impl<'a> MockEndpoint<'a, ReadMarkersEndpoint> {
3671 /// Returns a successful empty response.
3672 pub fn ok(self) -> MatrixMock<'a> {
3673 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3674 }
3675}
3676
3677/// A prebuilt mock for `PUT /user/{userId}/rooms/{roomId}/account_data/{type}`
3678/// request.
3679pub struct RoomAccountDataEndpoint;
3680
3681impl<'a> MockEndpoint<'a, RoomAccountDataEndpoint> {
3682 /// Returns a successful empty response.
3683 pub fn ok(self) -> MatrixMock<'a> {
3684 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3685 }
3686}
3687
3688/// A prebuilt mock for `GET /_matrix/client/v1/media/config` request.
3689pub struct AuthenticatedMediaConfigEndpoint;
3690
3691impl<'a> MockEndpoint<'a, AuthenticatedMediaConfigEndpoint> {
3692 /// Returns a successful response with the provided max upload size.
3693 pub fn ok(self, max_upload_size: UInt) -> MatrixMock<'a> {
3694 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3695 "m.upload.size": max_upload_size,
3696 })))
3697 }
3698
3699 /// Returns a successful response with a maxed out max upload size.
3700 pub fn ok_default(self) -> MatrixMock<'a> {
3701 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3702 "m.upload.size": UInt::MAX,
3703 })))
3704 }
3705}
3706
3707/// A prebuilt mock for `GET /_matrix/media/v3/config` request.
3708pub struct MediaConfigEndpoint;
3709
3710impl<'a> MockEndpoint<'a, MediaConfigEndpoint> {
3711 /// Returns a successful response with the provided max upload size.
3712 pub fn ok(self, max_upload_size: UInt) -> MatrixMock<'a> {
3713 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3714 "m.upload.size": max_upload_size,
3715 })))
3716 }
3717}
3718
3719/// A prebuilt mock for `POST /login` requests.
3720pub struct LoginEndpoint;
3721
3722impl<'a> MockEndpoint<'a, LoginEndpoint> {
3723 /// Returns a successful response.
3724 pub fn ok(self) -> MatrixMock<'a> {
3725 self.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::LOGIN))
3726 }
3727
3728 /// Returns a given response on POST /login requests
3729 ///
3730 /// # Arguments
3731 ///
3732 /// * `response` - The response that the mock server sends on POST /login
3733 /// requests.
3734 ///
3735 /// # Returns
3736 ///
3737 /// Returns a [`MatrixMock`] which can be mounted.
3738 ///
3739 /// # Examples
3740 ///
3741 /// ```
3742 /// use matrix_sdk::test_utils::mocks::{
3743 /// LoginResponseTemplate200, MatrixMockServer,
3744 /// };
3745 /// use matrix_sdk_test::async_test;
3746 /// use ruma::{device_id, time::Duration, user_id};
3747 ///
3748 /// #[async_test]
3749 /// async fn test_ok_with() {
3750 /// let server = MatrixMockServer::new().await;
3751 /// server
3752 /// .mock_login()
3753 /// .ok_with(LoginResponseTemplate200::new(
3754 /// "qwerty",
3755 /// device_id!("DEADBEEF"),
3756 /// user_id!("@cheeky_monkey:matrix.org"),
3757 /// ))
3758 /// .mount()
3759 /// .await;
3760 ///
3761 /// let client = server.client_builder().unlogged().build().await;
3762 ///
3763 /// let result = client
3764 /// .matrix_auth()
3765 /// .login_username("example", "wordpass")
3766 /// .send()
3767 /// .await
3768 /// .unwrap();
3769 ///
3770 /// assert!(
3771 /// result.access_tokesn.unwrap() == "qwerty",
3772 /// "wrong access token in response"
3773 /// );
3774 /// assert!(
3775 /// result.device_id.unwrap() == "DEADBEEF",
3776 /// "wrong device id in response"
3777 /// );
3778 /// assert!(
3779 /// result.user_id.unwrap() == "@cheeky_monkey:matrix.org",
3780 /// "wrong user id in response"
3781 /// );
3782 /// }
3783 /// ```
3784 pub fn ok_with(self, response: LoginResponseTemplate200) -> MatrixMock<'a> {
3785 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3786 "access_token": response.access_token,
3787 "device_id": response.device_id,
3788 "user_id": response.user_id,
3789 "expires_in": response.expires_in.map(|duration| { duration.as_millis() }),
3790 "refresh_token": response.refresh_token,
3791 "well_known": response.well_known.map(|vals| {
3792 json!({
3793 "m.homeserver": {
3794 "base_url": vals.homeserver_url
3795 },
3796 "m.identity_server": vals.identity_url.map(|url| {
3797 json!({
3798 "base_url": url
3799 })
3800 })
3801 })
3802 }),
3803 })))
3804 }
3805
3806 /// Ensures that the body of the request is a superset of the provided
3807 /// `body` parameter.
3808 pub fn body_matches_partial_json(self, body: Value) -> Self {
3809 Self { mock: self.mock.and(body_partial_json(body)), ..self }
3810 }
3811}
3812
3813#[derive(Default)]
3814struct LoginResponseWellKnown {
3815 /// Required if well_known is used: The base URL for the homeserver for
3816 /// client-server connections.
3817 homeserver_url: String,
3818
3819 /// Required if well_known and m.identity_server are used: The base URL for
3820 /// the identity server for client-server connections.
3821 identity_url: Option<String>,
3822}
3823
3824/// A response to a [`LoginEndpoint`] query with status code 200.
3825#[derive(Default)]
3826pub struct LoginResponseTemplate200 {
3827 /// Required: An access token for the account. This access token can then be
3828 /// used to authorize other requests.
3829 access_token: Option<String>,
3830
3831 /// Required: ID of the logged-in device. Will be the same as the
3832 /// corresponding parameter in the request, if one was specified.
3833 device_id: Option<OwnedDeviceId>,
3834
3835 /// The lifetime of the access token, in milliseconds. Once the access token
3836 /// has expired a new access token can be obtained by using the provided
3837 /// refresh token. If no refresh token is provided, the client will need
3838 /// to re-log in to obtain a new access token. If not given, the client
3839 /// can assume that the access token will not expire.
3840 expires_in: Option<Duration>,
3841
3842 /// A refresh token for the account. This token can be used to obtain a new
3843 /// access token when it expires by calling the /refresh endpoint.
3844 refresh_token: Option<String>,
3845
3846 /// Required: The fully-qualified Matrix ID for the account.
3847 user_id: Option<OwnedUserId>,
3848
3849 /// Optional client configuration provided by the server.
3850 well_known: Option<LoginResponseWellKnown>,
3851}
3852
3853impl LoginResponseTemplate200 {
3854 /// Constructor for empty response
3855 pub fn new<T1: Into<OwnedDeviceId>, T2: Into<OwnedUserId>>(
3856 access_token: &str,
3857 device_id: T1,
3858 user_id: T2,
3859 ) -> Self {
3860 Self {
3861 access_token: Some(access_token.to_owned()),
3862 device_id: Some(device_id.into()),
3863 user_id: Some(user_id.into()),
3864 ..Default::default()
3865 }
3866 }
3867
3868 /// sets expires_in
3869 pub fn expires_in(mut self, value: Duration) -> Self {
3870 self.expires_in = Some(value);
3871 self
3872 }
3873
3874 /// sets refresh_token
3875 pub fn refresh_token(mut self, value: &str) -> Self {
3876 self.refresh_token = Some(value.to_owned());
3877 self
3878 }
3879
3880 /// sets well_known which takes a homeserver_url and an optional
3881 /// identity_url
3882 pub fn well_known(mut self, homeserver_url: String, identity_url: Option<String>) -> Self {
3883 self.well_known = Some(LoginResponseWellKnown { homeserver_url, identity_url });
3884 self
3885 }
3886}
3887
3888/// A prebuilt mock for `GET /devices` requests.
3889pub struct DevicesEndpoint;
3890
3891impl<'a> MockEndpoint<'a, DevicesEndpoint> {
3892 /// Returns a successful response.
3893 pub fn ok(self) -> MatrixMock<'a> {
3894 self.respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::DEVICES))
3895 }
3896}
3897
3898/// A prebuilt mock for `POST /user_directory/search` requests.
3899pub struct UserDirectoryEndpoint;
3900
3901impl<'a> MockEndpoint<'a, UserDirectoryEndpoint> {
3902 /// Returns a successful response.
3903 pub fn ok(self) -> MatrixMock<'a> {
3904 self.respond_with(
3905 ResponseTemplate::new(200)
3906 .set_body_json(&*test_json::search_users::SEARCH_USERS_RESPONSE),
3907 )
3908 }
3909}
3910
3911/// A prebuilt mock for `POST /createRoom` requests.
3912pub struct CreateRoomEndpoint;
3913
3914impl<'a> MockEndpoint<'a, CreateRoomEndpoint> {
3915 /// Returns a successful response.
3916 pub fn ok(self) -> MatrixMock<'a> {
3917 self.respond_with(
3918 ResponseTemplate::new(200).set_body_json(json!({ "room_id": "!room:example.org"})),
3919 )
3920 }
3921}
3922
3923/// A prebuilt mock for `POST /rooms/{roomId}/upgrade` requests.
3924pub struct UpgradeRoomEndpoint;
3925
3926impl<'a> MockEndpoint<'a, UpgradeRoomEndpoint> {
3927 /// Returns a successful response with desired replacement_room ID.
3928 pub fn ok_with(self, new_room_id: &RoomId) -> MatrixMock<'a> {
3929 self.respond_with(
3930 ResponseTemplate::new(200)
3931 .set_body_json(json!({ "replacement_room": new_room_id.as_str()})),
3932 )
3933 }
3934}
3935
3936/// A prebuilt mock for `POST /media/v1/create` requests.
3937pub struct MediaAllocateEndpoint;
3938
3939impl<'a> MockEndpoint<'a, MediaAllocateEndpoint> {
3940 /// Returns a successful response.
3941 pub fn ok(self) -> MatrixMock<'a> {
3942 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
3943 "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw"
3944 })))
3945 }
3946}
3947
3948/// A prebuilt mock for `PUT /media/v3/upload/{server_name}/{media_id}`
3949/// requests.
3950pub struct MediaAllocatedUploadEndpoint;
3951
3952impl<'a> MockEndpoint<'a, MediaAllocatedUploadEndpoint> {
3953 /// Returns a successful response.
3954 pub fn ok(self) -> MatrixMock<'a> {
3955 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
3956 }
3957}
3958
3959/// A prebuilt mock for `GET /media/v3/download` requests.
3960pub struct MediaDownloadEndpoint;
3961
3962impl<'a> MockEndpoint<'a, MediaDownloadEndpoint> {
3963 /// Returns a successful response with a plain text content.
3964 pub fn ok_plain_text(self) -> MatrixMock<'a> {
3965 self.respond_with(ResponseTemplate::new(200).set_body_string("Hello, World!"))
3966 }
3967
3968 /// Returns a successful response with a fake image content.
3969 pub fn ok_image(self) -> MatrixMock<'a> {
3970 self.respond_with(
3971 ResponseTemplate::new(200).set_body_raw(b"binaryjpegfullimagedata", "image/jpeg"),
3972 )
3973 }
3974}
3975
3976/// A prebuilt mock for `GET /media/v3/thumbnail` requests.
3977pub struct MediaThumbnailEndpoint;
3978
3979impl<'a> MockEndpoint<'a, MediaThumbnailEndpoint> {
3980 /// Returns a successful response with a fake image content.
3981 pub fn ok(self) -> MatrixMock<'a> {
3982 self.respond_with(
3983 ResponseTemplate::new(200).set_body_raw(b"binaryjpegthumbnaildata", "image/jpeg"),
3984 )
3985 }
3986}
3987
3988/// A prebuilt mock for `GET /client/v1/media/download` requests.
3989pub struct AuthedMediaDownloadEndpoint;
3990
3991impl<'a> MockEndpoint<'a, AuthedMediaDownloadEndpoint> {
3992 /// Returns a successful response with a plain text content.
3993 pub fn ok_plain_text(self) -> MatrixMock<'a> {
3994 self.respond_with(ResponseTemplate::new(200).set_body_string("Hello, World!"))
3995 }
3996
3997 /// Returns a successful response with the given bytes.
3998 pub fn ok_bytes(self, bytes: Vec<u8>) -> MatrixMock<'a> {
3999 self.respond_with(
4000 ResponseTemplate::new(200).set_body_raw(bytes, "application/octet-stream"),
4001 )
4002 }
4003
4004 /// Returns a successful response with a fake image content.
4005 pub fn ok_image(self) -> MatrixMock<'a> {
4006 self.respond_with(
4007 ResponseTemplate::new(200).set_body_raw(b"binaryjpegfullimagedata", "image/jpeg"),
4008 )
4009 }
4010}
4011
4012/// A prebuilt mock for `GET /client/v1/media/thumbnail` requests.
4013pub struct AuthedMediaThumbnailEndpoint;
4014
4015impl<'a> MockEndpoint<'a, AuthedMediaThumbnailEndpoint> {
4016 /// Returns a successful response with a fake image content.
4017 pub fn ok(self) -> MatrixMock<'a> {
4018 self.respond_with(
4019 ResponseTemplate::new(200).set_body_raw(b"binaryjpegthumbnaildata", "image/jpeg"),
4020 )
4021 }
4022}
4023
4024/// A prebuilt mock for `GET /client/v3/rooms/{room_id}/join` requests.
4025pub struct JoinRoomEndpoint {
4026 room_id: OwnedRoomId,
4027}
4028
4029impl<'a> MockEndpoint<'a, JoinRoomEndpoint> {
4030 /// Returns a successful response using the provided [`RoomId`].
4031 pub fn ok(self) -> MatrixMock<'a> {
4032 let room_id = self.endpoint.room_id.to_owned();
4033
4034 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4035 "room_id": room_id,
4036 })))
4037 }
4038}
4039
4040#[derive(Default)]
4041struct ThreadSubscriptionMatchers {
4042 /// Optional room id to match in the query.
4043 room_id: Option<OwnedRoomId>,
4044 /// Optional thread root event id to match in the query.
4045 thread_root: Option<OwnedEventId>,
4046}
4047
4048impl ThreadSubscriptionMatchers {
4049 /// Match the request parameter against a specific room id.
4050 fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
4051 self.room_id = Some(room_id);
4052 self
4053 }
4054
4055 /// Match the request parameter against a specific thread root event id.
4056 fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
4057 self.thread_root = Some(thread_root);
4058 self
4059 }
4060
4061 /// Compute the final URI for the thread subscription endpoint.
4062 fn endpoint_regexp_uri(&self) -> String {
4063 if self.room_id.is_some() || self.thread_root.is_some() {
4064 format!(
4065 "^/_matrix/client/unstable/io.element.msc4306/rooms/{}/thread/{}/subscription$",
4066 self.room_id.as_deref().map(|s| s.as_str()).unwrap_or(".*"),
4067 self.thread_root.as_deref().map(|s| s.as_str()).unwrap_or(".*").replace("$", "\\$")
4068 )
4069 } else {
4070 "^/_matrix/client/unstable/io.element.msc4306/rooms/.*/thread/.*/subscription$"
4071 .to_owned()
4072 }
4073 }
4074}
4075
4076/// A prebuilt mock for `GET
4077/// /client/*/rooms/{room_id}/threads/{thread_root}/subscription`
4078#[derive(Default)]
4079pub struct RoomGetThreadSubscriptionEndpoint {
4080 matchers: ThreadSubscriptionMatchers,
4081}
4082
4083impl<'a> MockEndpoint<'a, RoomGetThreadSubscriptionEndpoint> {
4084 /// Returns a successful response for the given thread subscription.
4085 pub fn ok(mut self, automatic: bool) -> MatrixMock<'a> {
4086 self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
4087 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4088 "automatic": automatic
4089 })))
4090 }
4091
4092 /// Match the request parameter against a specific room id.
4093 pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
4094 self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
4095 self
4096 }
4097 /// Match the request parameter against a specific thread root event id.
4098 pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
4099 self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
4100 self
4101 }
4102}
4103
4104/// A prebuilt mock for `PUT
4105/// /client/*/rooms/{room_id}/threads/{thread_root}/subscription`
4106#[derive(Default)]
4107pub struct RoomPutThreadSubscriptionEndpoint {
4108 matchers: ThreadSubscriptionMatchers,
4109}
4110
4111impl<'a> MockEndpoint<'a, RoomPutThreadSubscriptionEndpoint> {
4112 /// Returns a successful response for the given setting of thread
4113 /// subscription.
4114 pub fn ok(mut self) -> MatrixMock<'a> {
4115 self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
4116 self.respond_with(ResponseTemplate::new(200))
4117 }
4118
4119 /// Returns that the server skipped an automated thread subscription,
4120 /// because the user unsubscribed to the thread after the event id passed in
4121 /// the automatic subscription.
4122 pub fn conflicting_unsubscription(mut self) -> MatrixMock<'a> {
4123 self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
4124 self.respond_with(ResponseTemplate::new(409).set_body_json(json!({
4125 "errcode": "IO.ELEMENT.MSC4306.M_CONFLICTING_UNSUBSCRIPTION",
4126 "error": "the user unsubscribed after the subscription event id"
4127 })))
4128 }
4129
4130 /// Match the request parameter against a specific room id.
4131 pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
4132 self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
4133 self
4134 }
4135 /// Match the request parameter against a specific thread root event id.
4136 pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
4137 self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
4138 self
4139 }
4140 /// Match the request body's `automatic` field against a specific event id.
4141 pub fn match_automatic_event_id(mut self, up_to_event_id: &EventId) -> Self {
4142 self.mock = self.mock.and(body_json(json!({
4143 "automatic": up_to_event_id
4144 })));
4145 self
4146 }
4147}
4148
4149/// A prebuilt mock for `DELETE
4150/// /client/*/rooms/{room_id}/threads/{thread_root}/subscription`
4151#[derive(Default)]
4152pub struct RoomDeleteThreadSubscriptionEndpoint {
4153 matchers: ThreadSubscriptionMatchers,
4154}
4155
4156impl<'a> MockEndpoint<'a, RoomDeleteThreadSubscriptionEndpoint> {
4157 /// Returns a successful response for the deletion of a given thread
4158 /// subscription.
4159 pub fn ok(mut self) -> MatrixMock<'a> {
4160 self.mock = self.mock.and(path_regex(self.endpoint.matchers.endpoint_regexp_uri()));
4161 self.respond_with(ResponseTemplate::new(200))
4162 }
4163
4164 /// Match the request parameter against a specific room id.
4165 pub fn match_room_id(mut self, room_id: OwnedRoomId) -> Self {
4166 self.endpoint.matchers = self.endpoint.matchers.match_room_id(room_id);
4167 self
4168 }
4169 /// Match the request parameter against a specific thread root event id.
4170 pub fn match_thread_id(mut self, thread_root: OwnedEventId) -> Self {
4171 self.endpoint.matchers = self.endpoint.matchers.match_thread_id(thread_root);
4172 self
4173 }
4174}
4175
4176/// A prebuilt mock for `PUT
4177/// /_matrix/client/v3/pushrules/global/{kind}/{ruleId}/enabled`.
4178pub struct EnablePushRuleEndpoint;
4179
4180impl<'a> MockEndpoint<'a, EnablePushRuleEndpoint> {
4181 /// Returns a successful empty JSON response.
4182 pub fn ok(self) -> MatrixMock<'a> {
4183 self.ok_empty_json()
4184 }
4185}
4186
4187/// A prebuilt mock for `PUT
4188/// /_matrix/client/v3/pushrules/global/{kind}/{ruleId}/actions`.
4189pub struct SetPushRulesActionsEndpoint;
4190
4191impl<'a> MockEndpoint<'a, SetPushRulesActionsEndpoint> {
4192 /// Returns a successful empty JSON response.
4193 pub fn ok(self) -> MatrixMock<'a> {
4194 self.ok_empty_json()
4195 }
4196}
4197
4198/// A prebuilt mock for `PUT
4199/// /_matrix/client/v3/pushrules/global/{kind}/{ruleId}`.
4200pub struct SetPushRulesEndpoint;
4201
4202impl<'a> MockEndpoint<'a, SetPushRulesEndpoint> {
4203 /// Returns a successful empty JSON response.
4204 pub fn ok(self) -> MatrixMock<'a> {
4205 self.ok_empty_json()
4206 }
4207}
4208
4209/// A prebuilt mock for `DELETE
4210/// /_matrix/client/v3/pushrules/global/{kind}/{ruleId}`.
4211pub struct DeletePushRulesEndpoint;
4212
4213impl<'a> MockEndpoint<'a, DeletePushRulesEndpoint> {
4214 /// Returns a successful empty JSON response.
4215 pub fn ok(self) -> MatrixMock<'a> {
4216 self.ok_empty_json()
4217 }
4218}
4219
4220/// A prebuilt mock for the federation version endpoint.
4221pub struct FederationVersionEndpoint;
4222
4223impl<'a> MockEndpoint<'a, FederationVersionEndpoint> {
4224 /// Returns a successful response with the given server name and version.
4225 pub fn ok(self, server_name: &str, version: &str) -> MatrixMock<'a> {
4226 let response_body = json!({
4227 "server": {
4228 "name": server_name,
4229 "version": version
4230 }
4231 });
4232 self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
4233 }
4234
4235 /// Returns a successful response with empty/missing server information.
4236 pub fn ok_empty(self) -> MatrixMock<'a> {
4237 let response_body = json!({});
4238 self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
4239 }
4240}
4241
4242/// A prebuilt mock for `GET ^/_matrix/client/v3/thread_subscriptions`.
4243#[derive(Default)]
4244pub struct GetThreadSubscriptionsEndpoint {
4245 /// New thread subscriptions per (room id, thread root event id).
4246 subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
4247 /// New thread unsubscriptions per (room id, thread root event id).
4248 unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
4249 /// Optional delay to respond to the query.
4250 delay: Option<Duration>,
4251}
4252
4253impl<'a> MockEndpoint<'a, GetThreadSubscriptionsEndpoint> {
4254 /// Add a single thread subscription to the response.
4255 pub fn add_subscription(
4256 mut self,
4257 room_id: OwnedRoomId,
4258 thread_root: OwnedEventId,
4259 subscription: ThreadSubscription,
4260 ) -> Self {
4261 self.endpoint.subscribed.entry(room_id).or_default().insert(thread_root, subscription);
4262 self
4263 }
4264
4265 /// Add a single thread unsubscription to the response.
4266 pub fn add_unsubcription(
4267 mut self,
4268 room_id: OwnedRoomId,
4269 thread_root: OwnedEventId,
4270 unsubscription: ThreadUnsubscription,
4271 ) -> Self {
4272 self.endpoint.unsubscribed.entry(room_id).or_default().insert(thread_root, unsubscription);
4273 self
4274 }
4275
4276 /// Respond with a given delay to the query.
4277 pub fn with_delay(mut self, delay: Duration) -> Self {
4278 self.endpoint.delay = Some(delay);
4279 self
4280 }
4281
4282 /// Match the `from` query parameter to a given value.
4283 pub fn match_from(self, from: &str) -> Self {
4284 Self { mock: self.mock.and(query_param("from", from)), ..self }
4285 }
4286 /// Match the `to` query parameter to a given value.
4287 pub fn match_to(self, to: &str) -> Self {
4288 Self { mock: self.mock.and(query_param("to", to)), ..self }
4289 }
4290
4291 /// Returns a successful response with the given thread subscriptions, and
4292 /// "end" parameter to be used in the next query.
4293 pub fn ok(self, end: Option<String>) -> MatrixMock<'a> {
4294 let response_body = json!({
4295 "subscribed": self.endpoint.subscribed,
4296 "unsubscribed": self.endpoint.unsubscribed,
4297 "end": end,
4298 });
4299
4300 let mut template = ResponseTemplate::new(200).set_body_json(response_body);
4301
4302 if let Some(delay) = self.endpoint.delay {
4303 template = template.set_delay(delay);
4304 }
4305
4306 self.respond_with(template)
4307 }
4308}
4309
4310/// A prebuilt mock for `GET /client/*/rooms/{roomId}/hierarchy`
4311#[derive(Default)]
4312pub struct GetHierarchyEndpoint;
4313
4314impl<'a> MockEndpoint<'a, GetHierarchyEndpoint> {
4315 /// Returns a successful response containing the given room IDs.
4316 pub fn ok_with_room_ids(self, room_ids: Vec<&RoomId>) -> MatrixMock<'a> {
4317 let rooms = room_ids
4318 .iter()
4319 .map(|id| {
4320 json!({
4321 "room_id": id,
4322 "num_joined_members": 1,
4323 "world_readable": false,
4324 "guest_can_join": false,
4325 "children_state": []
4326 })
4327 })
4328 .collect::<Vec<_>>();
4329
4330 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4331 "rooms": rooms,
4332 })))
4333 }
4334
4335 /// Returns a successful response containing the given room IDs and children
4336 /// states
4337 pub fn ok_with_room_ids_and_children_state(
4338 self,
4339 room_ids: Vec<&RoomId>,
4340 children_state: Vec<(&RoomId, Vec<&ServerName>)>,
4341 ) -> MatrixMock<'a> {
4342 let children_state = children_state
4343 .into_iter()
4344 .map(|(id, via)| {
4345 json!({
4346 "type":
4347 "m.space.child",
4348 "state_key": id,
4349 "content": { "via": via },
4350 "sender": "@bob:matrix.org",
4351 "origin_server_ts": MilliSecondsSinceUnixEpoch::now()
4352 })
4353 })
4354 .collect::<Vec<_>>();
4355
4356 let rooms = room_ids
4357 .iter()
4358 .map(|id| {
4359 json!({
4360 "room_id": id,
4361 "num_joined_members": 1,
4362 "world_readable": false,
4363 "guest_can_join": false,
4364 "children_state": children_state
4365 })
4366 })
4367 .collect::<Vec<_>>();
4368
4369 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4370 "rooms": rooms,
4371 })))
4372 }
4373
4374 /// Returns a successful response with an empty list of rooms.
4375 pub fn ok(self) -> MatrixMock<'a> {
4376 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4377 "rooms": []
4378 })))
4379 }
4380}
4381
4382/// A prebuilt mock for running simplified sliding sync.
4383pub struct SlidingSyncEndpoint;
4384
4385impl<'a> MockEndpoint<'a, SlidingSyncEndpoint> {
4386 /// Mocks the sliding sync endpoint with the given response.
4387 pub fn ok(self, response: v5::Response) -> MatrixMock<'a> {
4388 // A bit silly that we need to destructure all the fields ourselves, but
4389 // Response isn't serializable :'(
4390 self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
4391 "txn_id": response.txn_id,
4392 "pos": response.pos,
4393 "lists": response.lists,
4394 "rooms": response.rooms,
4395 "extensions": response.extensions,
4396 })))
4397 }
4398
4399 /// Temporarily mocks the sync with the given endpoint and runs a client
4400 /// sync with it.
4401 ///
4402 /// After calling this function, the sync endpoint isn't mocked anymore.
4403 pub async fn ok_and_run<F: FnOnce(SlidingSyncBuilder) -> SlidingSyncBuilder>(
4404 self,
4405 client: &Client,
4406 on_builder: F,
4407 response: v5::Response,
4408 ) {
4409 let _scope = self.ok(response).mount_as_scoped().await;
4410
4411 let sliding_sync =
4412 on_builder(client.sliding_sync("test_id").unwrap()).build().await.unwrap();
4413
4414 let _summary = sliding_sync.sync_once().await.unwrap();
4415 }
4416}