matrix_sdk/test_utils/mocks.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},
23};
24
25use matrix_sdk_base::deserialized_responses::TimelineEvent;
26use matrix_sdk_test::{
27 test_json, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder,
28 SyncResponseBuilder,
29};
30use percent_encoding::{AsciiSet, CONTROLS};
31use ruma::{
32 api::client::room::Visibility,
33 directory::PublicRoomsChunk,
34 events::{
35 room::member::RoomMemberEvent, AnyStateEvent, AnyTimelineEvent, MessageLikeEventType,
36 StateEventType,
37 },
38 serde::Raw,
39 time::Duration,
40 MxcUri, OwnedEventId, OwnedRoomId, RoomId, ServerName,
41};
42use serde::Deserialize;
43use serde_json::{json, Value};
44use wiremock::{
45 matchers::{body_partial_json, header, method, path, path_regex, query_param},
46 Mock, MockBuilder, MockGuard, MockServer, Request, Respond, ResponseTemplate, Times,
47};
48
49use super::client::MockClientBuilder;
50use crate::{Client, OwnedServerName, Room};
51
52/// A [`wiremock`] [`MockServer`] along with useful methods to help mocking
53/// Matrix client-server API endpoints easily.
54///
55/// It implements mock endpoints, limiting the shared code as much as possible,
56/// so the mocks are still flexible to use as scoped/unscoped mounts, named, and
57/// so on.
58///
59/// It works like this:
60///
61/// * start by saying which endpoint you'd like to mock, e.g.
62/// [`Self::mock_room_send()`]. This returns a specialized [`MockEndpoint`]
63/// data structure, with its own impl. For this example, it's
64/// `MockEndpoint<RoomSendEndpoint>`.
65/// * configure the response on the endpoint-specific mock data structure. For
66/// instance, if you want the sending to result in a transient failure, call
67/// [`MockEndpoint::error500`]; if you want it to succeed and return the event
68/// `$42`, call [`MockEndpoint::ok()`]. It's still possible to call
69/// [`MockEndpoint::respond_with()`], as we do with wiremock MockBuilder, for
70/// maximum flexibility when the helpers aren't sufficient.
71/// * once the endpoint's response is configured, for any mock builder, you get
72/// a [`MatrixMock`]; this is a plain [`wiremock::Mock`] with the server
73/// curried, so one doesn't have to pass it around when calling
74/// [`MatrixMock::mount()`] or [`MatrixMock::mount_as_scoped()`]. As such, it
75/// mostly defers its implementations to [`wiremock::Mock`] under the hood.
76///
77/// # Examples
78///
79/// ```
80/// # tokio_test::block_on(async {
81/// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
82/// use serde_json::json;
83///
84/// // First create the mock server and client pair.
85/// let mock_server = MatrixMockServer::new().await;
86/// let client = mock_server.client_builder().build().await;
87///
88/// // Let's say that our rooms are not encrypted.
89/// mock_server.mock_room_state_encryption().plain().mount().await;
90///
91/// // Let us get a room where we will send an event.
92/// let room = mock_server
93/// .sync_joined_room(&client, room_id!("!room_id:localhost"))
94/// .await;
95///
96/// // Now we mock the endpoint so we can actually send the event.
97/// let event_id = event_id!("$some_id");
98/// let send_guard = mock_server
99/// .mock_room_send()
100/// .ok(event_id)
101/// .expect(1)
102/// .mount_as_scoped()
103/// .await;
104///
105/// // And we send it out.
106/// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
107///
108/// assert_eq!(
109/// event_id,
110/// response.event_id,
111/// "The event ID we mocked should match the one we received when we sent the event"
112/// );
113/// # anyhow::Ok(()) });
114/// ```
115pub struct MatrixMockServer {
116 server: MockServer,
117
118 /// Make the sync response builder stateful, to keep in memory the batch
119 /// token and avoid the client ignoring subsequent responses after the first
120 /// one.
121 sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
122}
123
124impl MatrixMockServer {
125 /// Create a new [`wiremock`] server specialized for Matrix usage.
126 pub async fn new() -> Self {
127 let server = MockServer::start().await;
128 Self { server, sync_response_builder: Default::default() }
129 }
130
131 /// Creates a new [`MatrixMockServer`] from a [`wiremock`] server.
132 pub fn from_server(server: MockServer) -> Self {
133 Self { server, sync_response_builder: Default::default() }
134 }
135
136 /// Creates a new [`MockClientBuilder`] configured to use this server,
137 /// preconfigured with a session expected by the server endpoints.
138 pub fn client_builder(&self) -> MockClientBuilder {
139 MockClientBuilder::new(self.server.uri())
140 }
141
142 /// Return the underlying [`wiremock`] server.
143 pub fn server(&self) -> &MockServer {
144 &self.server
145 }
146
147 /// Overrides the sync/ endpoint with knowledge that the given
148 /// invited/joined/knocked/left room exists, runs a sync and returns the
149 /// given room.
150 ///
151 /// # Examples
152 ///
153 /// ```
154 /// # tokio_test::block_on(async {
155 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
156 /// use matrix_sdk_test::LeftRoomBuilder;
157 ///
158 /// let mock_server = MatrixMockServer::new().await;
159 /// let client = mock_server.client_builder().build().await;
160 ///
161 /// let left_room = mock_server
162 /// .sync_room(&client, LeftRoomBuilder::new(room_id!("!room_id:localhost")))
163 /// .await;
164 /// # anyhow::Ok(()) });
165 pub async fn sync_room(&self, client: &Client, room_data: impl Into<AnyRoomBuilder>) -> Room {
166 let any_room = room_data.into();
167 let room_id = any_room.room_id().to_owned();
168
169 self.mock_sync()
170 .ok_and_run(client, move |builder| match any_room {
171 AnyRoomBuilder::Invited(invited) => {
172 builder.add_invited_room(invited);
173 }
174 AnyRoomBuilder::Joined(joined) => {
175 builder.add_joined_room(joined);
176 }
177 AnyRoomBuilder::Left(left) => {
178 builder.add_left_room(left);
179 }
180 AnyRoomBuilder::Knocked(knocked) => {
181 builder.add_knocked_room(knocked);
182 }
183 })
184 .await;
185
186 client.get_room(&room_id).expect("look at me, the room is known now")
187 }
188
189 /// Overrides the sync/ endpoint with knowledge that the given room exists
190 /// in the joined state, runs a sync and returns the given room.
191 ///
192 /// # Examples
193 ///
194 /// ```
195 /// # tokio_test::block_on(async {
196 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
197 ///
198 /// let mock_server = MatrixMockServer::new().await;
199 /// let client = mock_server.client_builder().build().await;
200 ///
201 /// let room = mock_server
202 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
203 /// .await;
204 /// # anyhow::Ok(()) });
205 pub async fn sync_joined_room(&self, client: &Client, room_id: &RoomId) -> Room {
206 self.sync_room(client, JoinedRoomBuilder::new(room_id)).await
207 }
208
209 /// Verify that the previous mocks expected number of requests match
210 /// reality, and then cancels all active mocks.
211 ///
212 /// # Examples
213 ///
214 /// ```
215 /// # tokio_test::block_on(async {
216 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
217 /// use serde_json::json;
218 ///
219 /// let mock_server = MatrixMockServer::new().await;
220 /// let client = mock_server.client_builder().build().await;
221 ///
222 /// mock_server.mock_room_state_encryption().plain().mount().await;
223 /// let room = mock_server
224 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
225 /// .await;
226 /// mock_server.mock_room_send().ok(event_id!("$some_id")).mount().await;
227 ///
228 /// // This will succeed.
229 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
230 ///
231 /// // Now we reset the mocks.
232 /// mock_server.verify_and_reset().await;
233 ///
234 /// // And we can't send anymore.
235 /// let response = room
236 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
237 /// .await
238 /// .expect_err("We removed the mock so sending should now fail");
239 /// # anyhow::Ok(()) });
240 /// ```
241 pub async fn verify_and_reset(&self) {
242 self.server.verify().await;
243 self.server.reset().await;
244 }
245}
246
247// Specific mount endpoints.
248impl MatrixMockServer {
249 /// Mocks a sync endpoint.
250 ///
251 /// # Examples
252 ///
253 /// ```
254 /// # tokio_test::block_on(async {
255 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
256 /// use matrix_sdk_test::JoinedRoomBuilder;
257 ///
258 /// // First create the mock server and client pair.
259 /// let mock_server = MatrixMockServer::new().await;
260 /// let client = mock_server.client_builder().build().await;
261 /// let room_id = room_id!("!room_id:localhost");
262 ///
263 /// // Let's emulate what `MatrixMockServer::sync_joined_room()` does.
264 /// mock_server
265 /// .mock_sync()
266 /// .ok_and_run(&client, |builder| {
267 /// builder.add_joined_room(JoinedRoomBuilder::new(room_id));
268 /// })
269 /// .await;
270 ///
271 /// let room = client
272 /// .get_room(room_id)
273 /// .expect("The room should be available after we mocked the sync");
274 /// # anyhow::Ok(()) });
275 /// ```
276 pub fn mock_sync(&self) -> MockEndpoint<'_, SyncEndpoint> {
277 let mock = Mock::given(method("GET"))
278 .and(path("/_matrix/client/v3/sync"))
279 .and(header("authorization", "Bearer 1234"));
280 MockEndpoint {
281 mock,
282 server: &self.server,
283 endpoint: SyncEndpoint { sync_response_builder: self.sync_response_builder.clone() },
284 }
285 }
286
287 /// Creates a prebuilt mock for sending an event in a room.
288 ///
289 /// Note: works with *any* room.
290 ///
291 /// # Examples
292 ///
293 /// ```
294 /// # tokio_test::block_on(async {
295 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
296 /// use serde_json::json;
297 ///
298 /// let mock_server = MatrixMockServer::new().await;
299 /// let client = mock_server.client_builder().build().await;
300 ///
301 /// mock_server.mock_room_state_encryption().plain().mount().await;
302 ///
303 /// let room = mock_server
304 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
305 /// .await;
306 ///
307 /// let event_id = event_id!("$some_id");
308 /// mock_server
309 /// .mock_room_send()
310 /// .ok(event_id)
311 /// .expect(1)
312 /// .mount()
313 /// .await;
314 ///
315 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
316 ///
317 /// assert_eq!(
318 /// event_id,
319 /// response.event_id,
320 /// "The event ID we mocked should match the one we received when we sent the event"
321 /// );
322 /// # anyhow::Ok(()) });
323 /// ```
324 pub fn mock_room_send(&self) -> MockEndpoint<'_, RoomSendEndpoint> {
325 let mock = Mock::given(method("PUT"))
326 .and(header("authorization", "Bearer 1234"))
327 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/send/.*".to_owned()));
328 MockEndpoint { mock, server: &self.server, endpoint: RoomSendEndpoint }
329 }
330
331 /// Creates a prebuilt mock for sending a state event in a room.
332 ///
333 /// Similar to: [`MatrixMockServer::mock_room_send`]
334 ///
335 /// Note: works with *any* room.
336 /// Note: works with *any* event type.
337 ///
338 /// ```
339 /// # tokio_test::block_on(async {
340 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
341 /// use serde_json::json;
342 ///
343 /// let mock_server = MatrixMockServer::new().await;
344 /// let client = mock_server.client_builder().build().await;
345 ///
346 /// mock_server.mock_room_state_encryption().plain().mount().await;
347 ///
348 /// let room = mock_server
349 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
350 /// .await;
351 ///
352 /// let event_id = event_id!("$some_id");
353 /// mock_server
354 /// .mock_room_send_state()
355 /// .ok(event_id)
356 /// .expect(1)
357 /// .mount()
358 /// .await;
359 ///
360 /// let response_not_mocked = room.send_raw("m.room.create", json!({ "body": "Hello world" })).await;
361 /// // The `/send` endpoint should not be mocked by the server.
362 /// assert!(response_not_mocked.is_err());
363 ///
364 ///
365 /// let response = room.send_state_event_raw("m.room.message", "my_key", json!({ "body": "Hello world" })).await?;
366 /// // The `/state` endpoint should be mocked by the server.
367 /// assert_eq!(
368 /// event_id,
369 /// response.event_id,
370 /// "The event ID we mocked should match the one we received when we sent the event"
371 /// );
372 /// # anyhow::Ok(()) });
373 /// ```
374 pub fn mock_room_send_state(&self) -> MockEndpoint<'_, RoomSendStateEndpoint> {
375 let mock = Mock::given(method("PUT"))
376 .and(header("authorization", "Bearer 1234"))
377 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/.*/.*"));
378 MockEndpoint { mock, server: &self.server, endpoint: RoomSendStateEndpoint::default() }
379 }
380
381 /// Creates a prebuilt mock for asking whether *a* room is encrypted or not.
382 ///
383 /// Note: Applies to all rooms.
384 ///
385 /// # Examples
386 ///
387 /// ```
388 /// # tokio_test::block_on(async {
389 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
390 ///
391 /// let mock_server = MatrixMockServer::new().await;
392 /// let client = mock_server.client_builder().build().await;
393 ///
394 /// mock_server.mock_room_state_encryption().encrypted().mount().await;
395 ///
396 /// let room = mock_server
397 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
398 /// .await;
399 ///
400 /// assert!(
401 /// room.is_encrypted().await?,
402 /// "The room should be marked as encrypted."
403 /// );
404 /// # anyhow::Ok(()) });
405 /// ```
406 pub fn mock_room_state_encryption(&self) -> MockEndpoint<'_, EncryptionStateEndpoint> {
407 let mock = Mock::given(method("GET"))
408 .and(header("authorization", "Bearer 1234"))
409 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
410 MockEndpoint { mock, server: &self.server, endpoint: EncryptionStateEndpoint }
411 }
412
413 /// Creates a prebuilt mock for setting the room encryption state.
414 ///
415 /// Note: Applies to all rooms.
416 ///
417 /// # Examples
418 ///
419 /// ```
420 /// # tokio_test::block_on(async {
421 /// use matrix_sdk::{
422 /// ruma::{event_id, room_id},
423 /// test_utils::mocks::MatrixMockServer,
424 /// };
425 ///
426 /// let mock_server = MatrixMockServer::new().await;
427 /// let client = mock_server.client_builder().build().await;
428 ///
429 /// mock_server.mock_room_state_encryption().plain().mount().await;
430 /// mock_server
431 /// .mock_set_room_state_encryption()
432 /// .ok(event_id!("$id"))
433 /// .mock_once()
434 /// .mount()
435 /// .await;
436 ///
437 /// let room = mock_server
438 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
439 /// .await;
440 ///
441 /// room.enable_encryption()
442 /// .await
443 /// .expect("We should be able to enable encryption in the room");
444 /// # anyhow::Ok(()) });
445 /// ```
446 pub fn mock_set_room_state_encryption(&self) -> MockEndpoint<'_, SetEncryptionStateEndpoint> {
447 let mock = Mock::given(method("PUT"))
448 .and(header("authorization", "Bearer 1234"))
449 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.*room.*encryption.?"));
450 MockEndpoint { mock, server: &self.server, endpoint: SetEncryptionStateEndpoint }
451 }
452
453 /// Creates a prebuilt mock for the room redact endpoint.
454 ///
455 /// # Examples
456 ///
457 /// ```
458 /// # tokio_test::block_on(async {
459 /// use matrix_sdk::{
460 /// ruma::{event_id, room_id},
461 /// test_utils::mocks::MatrixMockServer,
462 /// };
463 ///
464 /// let mock_server = MatrixMockServer::new().await;
465 /// let client = mock_server.client_builder().build().await;
466 /// let event_id = event_id!("$id");
467 ///
468 /// mock_server.mock_room_redact().ok(event_id).mock_once().mount().await;
469 ///
470 /// let room = mock_server
471 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
472 /// .await;
473 ///
474 /// room.redact(event_id, None, None)
475 /// .await
476 /// .expect("We should be able to redact events in the room");
477 /// # anyhow::Ok(()) });
478 /// ```
479 pub fn mock_room_redact(&self) -> MockEndpoint<'_, RoomRedactEndpoint> {
480 let mock = Mock::given(method("PUT"))
481 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/redact/.*?/.*?"))
482 .and(header("authorization", "Bearer 1234"));
483 MockEndpoint { mock, server: &self.server, endpoint: RoomRedactEndpoint }
484 }
485
486 /// Creates a prebuilt mock for retrieving an event with /room/.../event.
487 pub fn mock_room_event(&self) -> MockEndpoint<'_, RoomEventEndpoint> {
488 let mock = Mock::given(method("GET")).and(header("authorization", "Bearer 1234"));
489 MockEndpoint {
490 mock,
491 server: &self.server,
492 endpoint: RoomEventEndpoint { room: None, match_event_id: false },
493 }
494 }
495
496 /// Create a prebuild mock for paginating room message with the `/messages`
497 /// endpoint.
498 pub fn mock_room_messages(&self) -> MockEndpoint<'_, RoomMessagesEndpoint> {
499 let mock = Mock::given(method("GET"))
500 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/messages$"))
501 .and(header("authorization", "Bearer 1234"));
502 MockEndpoint { mock, server: &self.server, endpoint: RoomMessagesEndpoint }
503 }
504
505 /// Create a prebuilt mock for uploading media.
506 pub fn mock_upload(&self) -> MockEndpoint<'_, UploadEndpoint> {
507 let mock = Mock::given(method("POST"))
508 .and(path("/_matrix/media/v3/upload"))
509 .and(header("authorization", "Bearer 1234"));
510 MockEndpoint { mock, server: &self.server, endpoint: UploadEndpoint }
511 }
512
513 /// Create a prebuilt mock for resolving room aliases.
514 ///
515 /// # Examples
516 ///
517 /// ```
518 /// # tokio_test::block_on(async {
519 /// use matrix_sdk::{
520 /// ruma::{owned_room_id, room_alias_id},
521 /// test_utils::mocks::MatrixMockServer,
522 /// };
523 /// let mock_server = MatrixMockServer::new().await;
524 /// let client = mock_server.client_builder().build().await;
525 ///
526 /// mock_server
527 /// .mock_room_directory_resolve_alias()
528 /// .ok("!a:b.c", Vec::new())
529 /// .mock_once()
530 /// .mount()
531 /// .await;
532 ///
533 /// let res = client
534 /// .resolve_room_alias(room_alias_id!("#a:b.c"))
535 /// .await
536 /// .expect("We should be able to resolve the room alias");
537 /// assert_eq!(res.room_id, owned_room_id!("!a:b.c"));
538 /// # anyhow::Ok(()) });
539 /// ```
540 pub fn mock_room_directory_resolve_alias(&self) -> MockEndpoint<'_, ResolveRoomAliasEndpoint> {
541 let mock =
542 Mock::given(method("GET")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
543 MockEndpoint { mock, server: &self.server, endpoint: ResolveRoomAliasEndpoint }
544 }
545
546 /// Create a prebuilt mock for publishing room aliases in the room
547 /// directory.
548 ///
549 /// # Examples
550 ///
551 /// ```
552 /// # tokio_test::block_on(async {
553 /// use matrix_sdk::{
554 /// ruma::{room_alias_id, room_id},
555 /// test_utils::mocks::MatrixMockServer,
556 /// };
557 ///
558 /// let mock_server = MatrixMockServer::new().await;
559 /// let client = mock_server.client_builder().build().await;
560 ///
561 /// mock_server
562 /// .mock_room_directory_create_room_alias()
563 /// .ok()
564 /// .mock_once()
565 /// .mount()
566 /// .await;
567 ///
568 /// client
569 /// .create_room_alias(room_alias_id!("#a:b.c"), room_id!("!a:b.c"))
570 /// .await
571 /// .expect("We should be able to create a room alias");
572 /// # anyhow::Ok(()) });
573 /// ```
574 pub fn mock_room_directory_create_room_alias(
575 &self,
576 ) -> MockEndpoint<'_, CreateRoomAliasEndpoint> {
577 let mock =
578 Mock::given(method("PUT")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
579 MockEndpoint { mock, server: &self.server, endpoint: CreateRoomAliasEndpoint }
580 }
581
582 /// Create a prebuilt mock for removing room aliases from the room
583 /// directory.
584 ///
585 /// # Examples
586 ///
587 /// ```
588 /// # tokio_test::block_on(async {
589 /// use matrix_sdk::{
590 /// ruma::room_alias_id, test_utils::mocks::MatrixMockServer,
591 /// };
592 ///
593 /// let mock_server = MatrixMockServer::new().await;
594 /// let client = mock_server.client_builder().build().await;
595 ///
596 /// mock_server
597 /// .mock_room_directory_remove_room_alias()
598 /// .ok()
599 /// .mock_once()
600 /// .mount()
601 /// .await;
602 ///
603 /// client
604 /// .remove_room_alias(room_alias_id!("#a:b.c"))
605 /// .await
606 /// .expect("We should be able to remove the room alias");
607 /// # anyhow::Ok(()) });
608 /// ```
609 pub fn mock_room_directory_remove_room_alias(
610 &self,
611 ) -> MockEndpoint<'_, RemoveRoomAliasEndpoint> {
612 let mock =
613 Mock::given(method("DELETE")).and(path_regex(r"/_matrix/client/v3/directory/room/.*"));
614 MockEndpoint { mock, server: &self.server, endpoint: RemoveRoomAliasEndpoint }
615 }
616
617 /// Create a prebuilt mock for listing public rooms.
618 ///
619 /// # Examples
620 ///
621 /// ```
622 /// #
623 /// tokio_test::block_on(async {
624 /// use js_int::uint;
625 /// use ruma::directory::PublicRoomsChunkInit;
626 /// use matrix_sdk::room_directory_search::RoomDirectorySearch;
627 /// use matrix_sdk::{
628 /// ruma::{event_id, room_id},
629 /// test_utils::mocks::MatrixMockServer,
630 /// };
631 /// let mock_server = MatrixMockServer::new().await;
632 /// let client = mock_server.client_builder().build().await;
633 /// let event_id = event_id!("$id");
634 /// let room_id = room_id!("!room_id:localhost");
635 ///
636 /// let chunk = vec![PublicRoomsChunkInit {
637 /// num_joined_members: uint!(0),
638 /// room_id: room_id.to_owned(),
639 /// world_readable: true,
640 /// guest_can_join: true,
641 /// }.into()];
642 ///
643 /// mock_server.mock_public_rooms().ok(chunk, None, None, Some(20)).mock_once().mount().await;
644 /// let mut room_directory_search = RoomDirectorySearch::new(client);
645 ///
646 /// room_directory_search.search(Some("some-alias".to_owned()), 100, None)
647 /// .await
648 /// .expect("Room directory search failed");
649 ///
650 /// let (results, _) = room_directory_search.results();
651 /// assert_eq!(results.len(), 1);
652 /// assert_eq!(results.get(0).unwrap().room_id, room_id.to_owned());
653 /// # });
654 /// ```
655 pub fn mock_public_rooms(&self) -> MockEndpoint<'_, PublicRoomsEndpoint> {
656 let mock = Mock::given(method("POST")).and(path_regex(r"/_matrix/client/v3/publicRooms"));
657 MockEndpoint { mock, server: &self.server, endpoint: PublicRoomsEndpoint }
658 }
659
660 /// Create a prebuilt mock for setting a room's visibility in the room
661 /// directory.
662 ///
663 /// # Examples
664 ///
665 /// ```
666 /// # tokio_test::block_on(async {
667 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
668 /// use ruma::api::client::room::Visibility;
669 ///
670 /// let mock_server = MatrixMockServer::new().await;
671 /// let client = mock_server.client_builder().build().await;
672 ///
673 /// mock_server
674 /// .mock_room_directory_set_room_visibility()
675 /// .ok()
676 /// .mock_once()
677 /// .mount()
678 /// .await;
679 ///
680 /// let room = mock_server
681 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
682 /// .await;
683 ///
684 /// room.privacy_settings()
685 /// .update_room_visibility(Visibility::Private)
686 /// .await
687 /// .expect("We should be able to update the room's visibility");
688 /// # anyhow::Ok(()) });
689 /// ```
690 pub fn mock_room_directory_set_room_visibility(
691 &self,
692 ) -> MockEndpoint<'_, SetRoomVisibilityEndpoint> {
693 let mock = Mock::given(method("PUT"))
694 .and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$"));
695 MockEndpoint { mock, server: &self.server, endpoint: SetRoomVisibilityEndpoint }
696 }
697
698 /// Create a prebuilt mock for getting a room's visibility in the room
699 /// directory.
700 ///
701 /// # Examples
702 ///
703 /// ```
704 /// # tokio_test::block_on(async {
705 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
706 /// use ruma::api::client::room::Visibility;
707 ///
708 /// let mock_server = MatrixMockServer::new().await;
709 /// let client = mock_server.client_builder().build().await;
710 ///
711 /// mock_server
712 /// .mock_room_directory_get_room_visibility()
713 /// .ok(Visibility::Public)
714 /// .mock_once()
715 /// .mount()
716 /// .await;
717 ///
718 /// let room = mock_server
719 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
720 /// .await;
721 ///
722 /// let visibility = room
723 /// .privacy_settings()
724 /// .get_room_visibility()
725 /// .await
726 /// .expect("We should be able to get the room's visibility");
727 /// assert_eq!(visibility, Visibility::Public);
728 /// # anyhow::Ok(()) });
729 /// ```
730 pub fn mock_room_directory_get_room_visibility(
731 &self,
732 ) -> MockEndpoint<'_, GetRoomVisibilityEndpoint> {
733 let mock = Mock::given(method("GET"))
734 .and(path_regex(r"^/_matrix/client/v3/directory/list/room/.*$"));
735 MockEndpoint { mock, server: &self.server, endpoint: GetRoomVisibilityEndpoint }
736 }
737
738 /// Create a prebuilt mock for fetching information about key storage
739 /// backups.
740 ///
741 /// # Examples
742 ///
743 /// ```
744 /// # #[cfg(feature = "e2e-encryption")]
745 /// # {
746 /// # tokio_test::block_on(async {
747 /// use matrix_sdk::test_utils::mocks::MatrixMockServer;
748 ///
749 /// let mock_server = MatrixMockServer::new().await;
750 /// let client = mock_server.client_builder().build().await;
751 ///
752 /// mock_server.mock_room_keys_version().exists().expect(1).mount().await;
753 ///
754 /// let exists =
755 /// client.encryption().backups().fetch_exists_on_server().await.unwrap();
756 ///
757 /// assert!(exists);
758 /// # });
759 /// # }
760 /// ```
761 pub fn mock_room_keys_version(&self) -> MockEndpoint<'_, RoomKeysVersionEndpoint> {
762 let mock = Mock::given(method("GET"))
763 .and(path_regex(r"_matrix/client/v3/room_keys/version"))
764 .and(header("authorization", "Bearer 1234"));
765 MockEndpoint { mock, server: &self.server, endpoint: RoomKeysVersionEndpoint }
766 }
767
768 /// Create a prebuilt mock for adding key storage backups via POST
769 pub fn mock_add_room_keys_version(&self) -> MockEndpoint<'_, AddRoomKeysVersionEndpoint> {
770 let mock = Mock::given(method("POST"))
771 .and(path_regex(r"_matrix/client/v3/room_keys/version"))
772 .and(header("authorization", "Bearer 1234"));
773 MockEndpoint { mock, server: &self.server, endpoint: AddRoomKeysVersionEndpoint }
774 }
775
776 /// Create a prebuilt mock for adding key storage backups via POST
777 pub fn mock_delete_room_keys_version(&self) -> MockEndpoint<'_, DeleteRoomKeysVersionEndpoint> {
778 let mock = Mock::given(method("DELETE"))
779 .and(path_regex(r"_matrix/client/v3/room_keys/version/[^/]*"))
780 .and(header("authorization", "Bearer 1234"));
781 MockEndpoint { mock, server: &self.server, endpoint: DeleteRoomKeysVersionEndpoint }
782 }
783
784 /// Create a prebuilt mock for getting the room members in a room.
785 ///
786 /// # Examples
787 ///
788 /// ```
789 /// # tokio_test::block_on(async {
790 /// use matrix_sdk::{
791 /// ruma::{event_id, room_id},
792 /// test_utils::mocks::MatrixMockServer,
793 /// };
794 /// use matrix_sdk_base::RoomMemberships;
795 /// use matrix_sdk_test::event_factory::EventFactory;
796 /// use ruma::{
797 /// events::room::member::{MembershipState, RoomMemberEventContent},
798 /// user_id,
799 /// };
800 /// let mock_server = MatrixMockServer::new().await;
801 /// let client = mock_server.client_builder().build().await;
802 /// let event_id = event_id!("$id");
803 /// let room_id = room_id!("!room_id:localhost");
804 ///
805 /// let f = EventFactory::new().room(room_id);
806 /// let alice_user_id = user_id!("@alice:b.c");
807 /// let alice_knock_event = f
808 /// .event(RoomMemberEventContent::new(MembershipState::Knock))
809 /// .event_id(event_id)
810 /// .sender(alice_user_id)
811 /// .state_key(alice_user_id)
812 /// .into_raw_timeline()
813 /// .cast();
814 ///
815 /// mock_server
816 /// .mock_get_members()
817 /// .ok(vec![alice_knock_event])
818 /// .mock_once()
819 /// .mount()
820 /// .await;
821 /// let room = mock_server.sync_joined_room(&client, room_id).await;
822 ///
823 /// let members = room.members(RoomMemberships::all()).await.unwrap();
824 /// assert_eq!(members.len(), 1);
825 /// # });
826 /// ```
827 pub fn mock_get_members(&self) -> MockEndpoint<'_, GetRoomMembersEndpoint> {
828 let mock =
829 Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/members$"));
830 MockEndpoint { mock, server: &self.server, endpoint: GetRoomMembersEndpoint }
831 }
832
833 /// Creates a prebuilt mock for inviting a user to a room by its id.
834 ///
835 /// # Examples
836 ///
837 /// ```
838 /// # use ruma::user_id;
839 /// tokio_test::block_on(async {
840 /// use matrix_sdk::{
841 /// ruma::room_id,
842 /// test_utils::mocks::MatrixMockServer,
843 /// };
844 ///
845 /// let mock_server = MatrixMockServer::new().await;
846 /// let client = mock_server.client_builder().build().await;
847 ///
848 /// mock_server.mock_invite_user_by_id().ok().mock_once().mount().await;
849 ///
850 /// let room = mock_server
851 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
852 /// .await;
853 ///
854 /// room.invite_user_by_id(user_id!("@alice:localhost")).await.unwrap();
855 /// # anyhow::Ok(()) });
856 /// ```
857 pub fn mock_invite_user_by_id(&self) -> MockEndpoint<'_, InviteUserByIdEndpoint> {
858 let mock =
859 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/invite$"));
860 MockEndpoint { mock, server: &self.server, endpoint: InviteUserByIdEndpoint }
861 }
862
863 /// Creates a prebuilt mock for kicking a user from a room.
864 ///
865 /// # Examples
866 ///
867 /// ```
868 /// # use ruma::user_id;
869 /// tokio_test::block_on(async {
870 /// use matrix_sdk::{
871 /// ruma::room_id,
872 /// test_utils::mocks::MatrixMockServer,
873 /// };
874 ///
875 /// let mock_server = MatrixMockServer::new().await;
876 /// let client = mock_server.client_builder().build().await;
877 ///
878 /// mock_server.mock_kick_user().ok().mock_once().mount().await;
879 ///
880 /// let room = mock_server
881 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
882 /// .await;
883 ///
884 /// room.kick_user(user_id!("@alice:localhost"), None).await.unwrap();
885 /// # anyhow::Ok(()) });
886 /// ```
887 pub fn mock_kick_user(&self) -> MockEndpoint<'_, KickUserEndpoint> {
888 let mock =
889 Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/kick"));
890 MockEndpoint { mock, server: &self.server, endpoint: KickUserEndpoint }
891 }
892
893 /// Creates a prebuilt mock for banning a user from a room.
894 ///
895 /// # Examples
896 ///
897 /// ```
898 /// # use ruma::user_id;
899 /// tokio_test::block_on(async {
900 /// use matrix_sdk::{
901 /// ruma::room_id,
902 /// test_utils::mocks::MatrixMockServer,
903 /// };
904 ///
905 /// let mock_server = MatrixMockServer::new().await;
906 /// let client = mock_server.client_builder().build().await;
907 ///
908 /// mock_server.mock_ban_user().ok().mock_once().mount().await;
909 ///
910 /// let room = mock_server
911 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
912 /// .await;
913 ///
914 /// room.ban_user(user_id!("@alice:localhost"), None).await.unwrap();
915 /// # anyhow::Ok(()) });
916 /// ```
917 pub fn mock_ban_user(&self) -> MockEndpoint<'_, BanUserEndpoint> {
918 let mock = Mock::given(method("POST")).and(path_regex(r"^/_matrix/client/v3/rooms/.*/ban"));
919 MockEndpoint { mock, server: &self.server, endpoint: BanUserEndpoint }
920 }
921
922 /// Creates a prebuilt mock for the `/_matrix/client/versions` endpoint.
923 pub fn mock_versions(&self) -> MockEndpoint<'_, VersionsEndpoint> {
924 let mock = Mock::given(method("GET")).and(path_regex(r"^/_matrix/client/versions"));
925 MockEndpoint { mock, server: &self.server, endpoint: VersionsEndpoint }
926 }
927
928 /// Creates a prebuilt mock for the room summary endpoint [MSC3266](https://github.com/matrix-org/matrix-spec-proposals/pull/3266).
929 pub fn mock_room_summary(&self) -> MockEndpoint<'_, RoomSummaryEndpoint> {
930 let mock = Mock::given(method("GET"))
931 .and(path_regex(r"^/_matrix/client/unstable/im.nheko.summary/rooms/.*/summary"));
932 MockEndpoint { mock, server: &self.server, endpoint: RoomSummaryEndpoint }
933 }
934
935 /// Creates a prebuilt mock for the endpoint used to set a room's pinned
936 /// events.
937 pub fn mock_set_room_pinned_events(&self) -> MockEndpoint<'_, SetRoomPinnedEventsEndpoint> {
938 let mock = Mock::given(method("PUT"))
939 .and(path_regex(r"^/_matrix/client/v3/rooms/.*/state/m.room.pinned_events/.*?"))
940 .and(header("authorization", "Bearer 1234"));
941 MockEndpoint { mock, server: &self.server, endpoint: SetRoomPinnedEventsEndpoint }
942 }
943}
944
945/// Parameter to [`MatrixMockServer::sync_room`].
946pub enum AnyRoomBuilder {
947 /// A room we've been invited to.
948 Invited(InvitedRoomBuilder),
949 /// A room we've joined.
950 Joined(JoinedRoomBuilder),
951 /// A room we've left.
952 Left(LeftRoomBuilder),
953 /// A room we've knocked to.
954 Knocked(KnockedRoomBuilder),
955}
956
957impl AnyRoomBuilder {
958 /// Get the [`RoomId`] of the room this [`AnyRoomBuilder`] will create.
959 fn room_id(&self) -> &RoomId {
960 match self {
961 AnyRoomBuilder::Invited(r) => r.room_id(),
962 AnyRoomBuilder::Joined(r) => r.room_id(),
963 AnyRoomBuilder::Left(r) => r.room_id(),
964 AnyRoomBuilder::Knocked(r) => r.room_id(),
965 }
966 }
967}
968
969impl From<InvitedRoomBuilder> for AnyRoomBuilder {
970 fn from(val: InvitedRoomBuilder) -> AnyRoomBuilder {
971 AnyRoomBuilder::Invited(val)
972 }
973}
974
975impl From<JoinedRoomBuilder> for AnyRoomBuilder {
976 fn from(val: JoinedRoomBuilder) -> AnyRoomBuilder {
977 AnyRoomBuilder::Joined(val)
978 }
979}
980
981impl From<LeftRoomBuilder> for AnyRoomBuilder {
982 fn from(val: LeftRoomBuilder) -> AnyRoomBuilder {
983 AnyRoomBuilder::Left(val)
984 }
985}
986
987impl From<KnockedRoomBuilder> for AnyRoomBuilder {
988 fn from(val: KnockedRoomBuilder) -> AnyRoomBuilder {
989 AnyRoomBuilder::Knocked(val)
990 }
991}
992
993/// The [path percent-encode set] as defined in the WHATWG URL standard + `/`
994/// since we always encode single segments of the path.
995///
996/// [path percent-encode set]: https://url.spec.whatwg.org/#path-percent-encode-set
997///
998/// Copied from Ruma:
999/// https://github.com/ruma/ruma/blob/e4cb409ff3aaa16f31a7fe1e61fee43b2d144f7b/crates/ruma-common/src/percent_encode.rs#L7
1000const PATH_PERCENT_ENCODE_SET: &AsciiSet = &CONTROLS
1001 .add(b' ')
1002 .add(b'"')
1003 .add(b'#')
1004 .add(b'<')
1005 .add(b'>')
1006 .add(b'?')
1007 .add(b'`')
1008 .add(b'{')
1009 .add(b'}')
1010 .add(b'/');
1011
1012fn percent_encoded_path(path: &str) -> String {
1013 percent_encoding::utf8_percent_encode(path, PATH_PERCENT_ENCODE_SET).to_string()
1014}
1015
1016/// A wrapper for a [`Mock`] as well as a [`MockServer`], allowing us to call
1017/// [`Mock::mount`] or [`Mock::mount_as_scoped`] without having to pass the
1018/// [`MockServer`] reference (i.e. call `mount()` instead of `mount(&server)`).
1019pub struct MatrixMock<'a> {
1020 mock: Mock,
1021 server: &'a MockServer,
1022}
1023
1024impl MatrixMock<'_> {
1025 /// Set an expectation on the number of times this [`MatrixMock`] should
1026 /// match in the current test case.
1027 ///
1028 /// Expectations are verified when the server is shutting down: if
1029 /// the expectation is not satisfied, the [`MatrixMockServer`] will panic
1030 /// and the `error_message` is shown.
1031 ///
1032 /// By default, no expectation is set for [`MatrixMock`]s.
1033 pub fn expect<T: Into<Times>>(self, num_calls: T) -> Self {
1034 Self { mock: self.mock.expect(num_calls), ..self }
1035 }
1036
1037 /// Assign a name to your mock.
1038 ///
1039 /// The mock name will be used in error messages (e.g. if the mock
1040 /// expectation is not satisfied) and debug logs to help you identify
1041 /// what failed.
1042 pub fn named(self, name: impl Into<String>) -> Self {
1043 Self { mock: self.mock.named(name), ..self }
1044 }
1045
1046 /// Respond to a response of this endpoint exactly once.
1047 ///
1048 /// After it's been called, subsequent responses will hit the next handler
1049 /// or a 404.
1050 ///
1051 /// Also verifies that it's been called once.
1052 pub fn mock_once(self) -> Self {
1053 Self { mock: self.mock.up_to_n_times(1).expect(1), ..self }
1054 }
1055
1056 /// Makes sure the endpoint is never reached.
1057 pub fn never(self) -> Self {
1058 Self { mock: self.mock.expect(0), ..self }
1059 }
1060
1061 /// Specify an upper limit to the number of times you would like this
1062 /// [`MatrixMock`] to respond to incoming requests that satisfy the
1063 /// conditions imposed by your matchers.
1064 pub fn up_to_n_times(self, num: u64) -> Self {
1065 Self { mock: self.mock.up_to_n_times(num), ..self }
1066 }
1067
1068 /// Mount a [`MatrixMock`] on the attached server.
1069 ///
1070 /// The [`MatrixMock`] will remain active until the [`MatrixMockServer`] is
1071 /// shut down. If you want to control or limit how long your
1072 /// [`MatrixMock`] stays active, check out [`Self::mount_as_scoped`].
1073 pub async fn mount(self) {
1074 self.mock.mount(self.server).await;
1075 }
1076
1077 /// Mount a [`MatrixMock`] as **scoped** on the attached server.
1078 ///
1079 /// When using [`Self::mount`], your [`MatrixMock`]s will be active until
1080 /// the [`MatrixMockServer`] is shut down.
1081 ///
1082 /// When using `mount_as_scoped`, your [`MatrixMock`]s will be active as
1083 /// long as the returned [`MockGuard`] is not dropped.
1084 ///
1085 /// When the returned [`MockGuard`] is dropped, [`MatrixMockServer`] will
1086 /// verify that the expectations set on the scoped [`MatrixMock`] were
1087 /// verified - if not, it will panic.
1088 pub async fn mount_as_scoped(self) -> MockGuard {
1089 self.mock.mount_as_scoped(self.server).await
1090 }
1091}
1092
1093/// Generic mocked endpoint, with useful common helpers.
1094pub struct MockEndpoint<'a, T> {
1095 server: &'a MockServer,
1096 mock: MockBuilder,
1097 endpoint: T,
1098}
1099
1100impl<'a, T> MockEndpoint<'a, T> {
1101 /// Specify how to respond to a query (viz., like
1102 /// [`MockBuilder::respond_with`] does), when other predefined responses
1103 /// aren't sufficient.
1104 ///
1105 /// # Examples
1106 ///
1107 /// ```
1108 /// # tokio_test::block_on(async {
1109 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1110 /// use serde_json::json;
1111 /// use wiremock::ResponseTemplate;
1112 ///
1113 /// let mock_server = MatrixMockServer::new().await;
1114 /// let client = mock_server.client_builder().build().await;
1115 ///
1116 /// mock_server.mock_room_state_encryption().plain().mount().await;
1117 ///
1118 /// let room = mock_server
1119 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1120 /// .await;
1121 ///
1122 /// let event_id = event_id!("$some_id");
1123 /// mock_server
1124 /// .mock_room_send()
1125 /// .respond_with(
1126 /// ResponseTemplate::new(429)
1127 /// .insert_header("Retry-After", "100")
1128 /// .set_body_json(json!({
1129 /// "errcode": "M_LIMIT_EXCEEDED",
1130 /// "custom_field": "with custom data",
1131 /// })))
1132 /// .expect(1)
1133 /// .mount()
1134 /// .await;
1135 ///
1136 /// room
1137 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1138 /// .await
1139 /// .expect_err("The sending of the event should fail");
1140 /// # anyhow::Ok(()) });
1141 /// ```
1142 pub fn respond_with<R: Respond + 'static>(self, func: R) -> MatrixMock<'a> {
1143 MatrixMock { mock: self.mock.respond_with(func), server: self.server }
1144 }
1145
1146 /// Returns a send endpoint that emulates a transient failure, i.e responds
1147 /// with error 500.
1148 ///
1149 /// # Examples
1150 /// ```
1151 /// # tokio_test::block_on(async {
1152 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1153 /// use serde_json::json;
1154 ///
1155 /// let mock_server = MatrixMockServer::new().await;
1156 /// let client = mock_server.client_builder().build().await;
1157 ///
1158 /// mock_server.mock_room_state_encryption().plain().mount().await;
1159 ///
1160 /// let room = mock_server
1161 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1162 /// .await;
1163 ///
1164 /// mock_server
1165 /// .mock_room_send()
1166 /// .error500()
1167 /// .expect(1)
1168 /// .mount()
1169 /// .await;
1170 ///
1171 /// room
1172 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1173 /// .await.expect_err("The sending of the event should have failed");
1174 /// # anyhow::Ok(()) });
1175 /// ```
1176 pub fn error500(self) -> MatrixMock<'a> {
1177 MatrixMock { mock: self.mock.respond_with(ResponseTemplate::new(500)), server: self.server }
1178 }
1179
1180 /// Internal helper to return an `{ event_id }` JSON struct along with a 200
1181 /// ok response.
1182 fn ok_with_event_id(self, event_id: OwnedEventId) -> MatrixMock<'a> {
1183 let mock = self.mock.respond_with(
1184 ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })),
1185 );
1186 MatrixMock { server: self.server, mock }
1187 }
1188
1189 /// Returns an endpoint that emulates a permanent failure error (e.g. event
1190 /// is too large).
1191 ///
1192 /// # Examples
1193 /// ```
1194 /// # tokio_test::block_on(async {
1195 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1196 /// use serde_json::json;
1197 ///
1198 /// let mock_server = MatrixMockServer::new().await;
1199 /// let client = mock_server.client_builder().build().await;
1200 ///
1201 /// mock_server.mock_room_state_encryption().plain().mount().await;
1202 ///
1203 /// let room = mock_server
1204 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1205 /// .await;
1206 ///
1207 /// mock_server
1208 /// .mock_room_send()
1209 /// .error_too_large()
1210 /// .expect(1)
1211 /// .mount()
1212 /// .await;
1213 ///
1214 /// room
1215 /// .send_raw("m.room.message", json!({ "body": "Hello world" }))
1216 /// .await.expect_err("The sending of the event should have failed");
1217 /// # anyhow::Ok(()) });
1218 /// ```
1219 pub fn error_too_large(self) -> MatrixMock<'a> {
1220 MatrixMock {
1221 mock: self.mock.respond_with(ResponseTemplate::new(413).set_body_json(json!({
1222 // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response
1223 "errcode": "M_TOO_LARGE",
1224 }))),
1225 server: self.server,
1226 }
1227 }
1228}
1229
1230/// A prebuilt mock for sending a message like event in a room.
1231pub struct RoomSendEndpoint;
1232
1233impl<'a> MockEndpoint<'a, RoomSendEndpoint> {
1234 /// Ensures that the body of the request is a superset of the provided
1235 /// `body` parameter.
1236 ///
1237 /// # Examples
1238 /// ```
1239 /// # tokio_test::block_on(async {
1240 /// use matrix_sdk::{
1241 /// ruma::{room_id, event_id, events::room::message::RoomMessageEventContent},
1242 /// test_utils::mocks::MatrixMockServer
1243 /// };
1244 /// use serde_json::json;
1245 ///
1246 /// let mock_server = MatrixMockServer::new().await;
1247 /// let client = mock_server.client_builder().build().await;
1248 ///
1249 /// mock_server.mock_room_state_encryption().plain().mount().await;
1250 ///
1251 /// let room = mock_server
1252 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1253 /// .await;
1254 ///
1255 /// let event_id = event_id!("$some_id");
1256 /// mock_server
1257 /// .mock_room_send()
1258 /// .body_matches_partial_json(json!({
1259 /// "body": "Hello world",
1260 /// }))
1261 /// .ok(event_id)
1262 /// .expect(1)
1263 /// .mount()
1264 /// .await;
1265 ///
1266 /// let content = RoomMessageEventContent::text_plain("Hello world");
1267 /// let response = room.send(content).await?;
1268 ///
1269 /// assert_eq!(
1270 /// event_id,
1271 /// response.event_id,
1272 /// "The event ID we mocked should match the one we received when we sent the event"
1273 /// );
1274 /// # anyhow::Ok(()) });
1275 /// ```
1276 pub fn body_matches_partial_json(self, body: Value) -> Self {
1277 Self { mock: self.mock.and(body_partial_json(body)), ..self }
1278 }
1279
1280 /// Ensures that the send endpoint request uses a specific event type.
1281 ///
1282 /// # Examples
1283 ///
1284 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1285 ///
1286 /// ```
1287 /// # tokio_test::block_on(async {
1288 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1289 /// use serde_json::json;
1290 ///
1291 /// let mock_server = MatrixMockServer::new().await;
1292 /// let client = mock_server.client_builder().build().await;
1293 ///
1294 /// mock_server.mock_room_state_encryption().plain().mount().await;
1295 ///
1296 /// let room = mock_server
1297 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1298 /// .await;
1299 ///
1300 /// let event_id = event_id!("$some_id");
1301 /// mock_server
1302 /// .mock_room_send()
1303 /// .for_type("m.room.message".into())
1304 /// .ok(event_id)
1305 /// .expect(1)
1306 /// .mount()
1307 /// .await;
1308 ///
1309 /// let response_not_mocked = room.send_raw("m.room.reaction", json!({ "body": "Hello world" })).await;
1310 /// // The `m.room.reaction` event type should not be mocked by the server.
1311 /// assert!(response_not_mocked.is_err());
1312 ///
1313 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
1314 /// // The `m.room.message` event type should be mocked by the server.
1315 /// assert_eq!(
1316 /// event_id,
1317 /// response.event_id,
1318 /// "The event ID we mocked should match the one we received when we sent the event"
1319 /// );
1320 /// # anyhow::Ok(()) });
1321 /// ```
1322 pub fn for_type(self, event_type: MessageLikeEventType) -> Self {
1323 Self {
1324 // Note: we already defined a path when constructing the mock builder, but this one
1325 // ought to be more specialized.
1326 mock: self
1327 .mock
1328 .and(path_regex(format!(r"^/_matrix/client/v3/rooms/.*/send/{event_type}",))),
1329 ..self
1330 }
1331 }
1332
1333 /// Ensures the event was sent as a delayed event.
1334 ///
1335 /// Note: works with *any* room.
1336 ///
1337 /// # Examples
1338 ///
1339 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1340 ///
1341 /// ```
1342 /// # tokio_test::block_on(async {
1343 /// use matrix_sdk::{
1344 /// ruma::{
1345 /// api::client::delayed_events::{delayed_message_event, DelayParameters},
1346 /// events::{message::MessageEventContent, AnyMessageLikeEventContent},
1347 /// room_id,
1348 /// time::Duration,
1349 /// TransactionId,
1350 /// },
1351 /// test_utils::mocks::MatrixMockServer,
1352 /// };
1353 /// use serde_json::json;
1354 /// use wiremock::ResponseTemplate;
1355 ///
1356 /// let mock_server = MatrixMockServer::new().await;
1357 /// let client = mock_server.client_builder().build().await;
1358 ///
1359 /// mock_server.mock_room_state_encryption().plain().mount().await;
1360 ///
1361 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
1362 ///
1363 /// mock_server
1364 /// .mock_room_send()
1365 /// .with_delay(Duration::from_millis(500))
1366 /// .respond_with(ResponseTemplate::new(200).set_body_json(json!({"delay_id":"$some_id"})))
1367 /// .mock_once()
1368 /// .mount()
1369 /// .await;
1370 ///
1371 /// let response_not_mocked =
1372 /// room.send_raw("m.room.message", json!({ "body": "Hello world" })).await;
1373 ///
1374 /// // A non delayed event should not be mocked by the server.
1375 /// assert!(response_not_mocked.is_err());
1376 ///
1377 /// let r = delayed_message_event::unstable::Request::new(
1378 /// room.room_id().to_owned(),
1379 /// TransactionId::new(),
1380 /// DelayParameters::Timeout { timeout: Duration::from_millis(500) },
1381 /// &AnyMessageLikeEventContent::Message(MessageEventContent::plain("hello world")),
1382 /// )
1383 /// .unwrap();
1384 ///
1385 /// let response = room.client().send(r).await.unwrap();
1386 /// // The delayed `m.room.message` event type should be mocked by the server.
1387 /// assert_eq!("$some_id", response.delay_id);
1388 /// # anyhow::Ok(()) });
1389 /// ```
1390 pub fn with_delay(self, delay: Duration) -> Self {
1391 Self {
1392 mock: self
1393 .mock
1394 .and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
1395 ..self
1396 }
1397 }
1398
1399 /// Returns a send endpoint that emulates success, i.e. the event has been
1400 /// sent with the given event id.
1401 ///
1402 /// # Examples
1403 /// ```
1404 /// # tokio_test::block_on(async {
1405 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1406 /// use serde_json::json;
1407 ///
1408 /// let mock_server = MatrixMockServer::new().await;
1409 /// let client = mock_server.client_builder().build().await;
1410 ///
1411 /// mock_server.mock_room_state_encryption().plain().mount().await;
1412 ///
1413 /// let room = mock_server
1414 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1415 /// .await;
1416 ///
1417 /// let event_id = event_id!("$some_id");
1418 /// let send_guard = mock_server
1419 /// .mock_room_send()
1420 /// .ok(event_id)
1421 /// .expect(1)
1422 /// .mount_as_scoped()
1423 /// .await;
1424 ///
1425 /// let response = room.send_raw("m.room.message", json!({ "body": "Hello world" })).await?;
1426 ///
1427 /// assert_eq!(
1428 /// event_id,
1429 /// response.event_id,
1430 /// "The event ID we mocked should match the one we received when we sent the event"
1431 /// );
1432 /// # anyhow::Ok(()) });
1433 /// ```
1434 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
1435 self.ok_with_event_id(returned_event_id.into())
1436 }
1437}
1438
1439/// A prebuilt mock for sending a state event in a room.
1440#[derive(Default)]
1441pub struct RoomSendStateEndpoint {
1442 state_key: Option<String>,
1443 event_type: Option<StateEventType>,
1444}
1445
1446impl<'a> MockEndpoint<'a, RoomSendStateEndpoint> {
1447 fn generate_path_regexp(endpoint: &RoomSendStateEndpoint) -> String {
1448 format!(
1449 r"^/_matrix/client/v3/rooms/.*/state/{}/{}",
1450 endpoint.event_type.as_ref().map_or_else(|| ".*".to_owned(), |t| t.to_string()),
1451 endpoint.state_key.as_ref().map_or_else(|| ".*".to_owned(), |k| k.to_string())
1452 )
1453 }
1454
1455 /// Ensures that the body of the request is a superset of the provided
1456 /// `body` parameter.
1457 ///
1458 /// # Examples
1459 /// ```
1460 /// # tokio_test::block_on(async {
1461 /// use matrix_sdk::{
1462 /// ruma::{room_id, event_id, events::room::power_levels::RoomPowerLevelsEventContent},
1463 /// test_utils::mocks::MatrixMockServer
1464 /// };
1465 /// use serde_json::json;
1466 ///
1467 /// let mock_server = MatrixMockServer::new().await;
1468 /// let client = mock_server.client_builder().build().await;
1469 ///
1470 /// mock_server.mock_room_state_encryption().plain().mount().await;
1471 ///
1472 /// let room = mock_server
1473 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1474 /// .await;
1475 ///
1476 /// let event_id = event_id!("$some_id");
1477 /// mock_server
1478 /// .mock_room_send_state()
1479 /// .body_matches_partial_json(json!({
1480 /// "redact": 51,
1481 /// }))
1482 /// .ok(event_id)
1483 /// .expect(1)
1484 /// .mount()
1485 /// .await;
1486 ///
1487 /// let mut content = RoomPowerLevelsEventContent::new();
1488 /// // Update the power level to a non default value.
1489 /// // Otherwise it will be skipped from serialization.
1490 /// content.redact = 51.into();
1491 ///
1492 /// let response = room.send_state_event(content).await?;
1493 ///
1494 /// assert_eq!(
1495 /// event_id,
1496 /// response.event_id,
1497 /// "The event ID we mocked should match the one we received when we sent the event"
1498 /// );
1499 /// # anyhow::Ok(()) });
1500 /// ```
1501 pub fn body_matches_partial_json(self, body: Value) -> Self {
1502 Self { mock: self.mock.and(body_partial_json(body)), ..self }
1503 }
1504
1505 /// Ensures that the send endpoint request uses a specific event type.
1506 ///
1507 /// Note: works with *any* room.
1508 ///
1509 /// # Examples
1510 ///
1511 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1512 ///
1513 /// ```
1514 /// # tokio_test::block_on(async {
1515 /// use matrix_sdk::{
1516 /// ruma::{
1517 /// event_id,
1518 /// events::room::{
1519 /// create::RoomCreateEventContent, power_levels::RoomPowerLevelsEventContent,
1520 /// },
1521 /// events::StateEventType,
1522 /// room_id,
1523 /// },
1524 /// test_utils::mocks::MatrixMockServer,
1525 /// };
1526 ///
1527 /// let mock_server = MatrixMockServer::new().await;
1528 /// let client = mock_server.client_builder().build().await;
1529 ///
1530 /// mock_server.mock_room_state_encryption().plain().mount().await;
1531 ///
1532 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
1533 ///
1534 /// let event_id = event_id!("$some_id");
1535 ///
1536 /// mock_server
1537 /// .mock_room_send_state()
1538 /// .for_type(StateEventType::RoomPowerLevels)
1539 /// .ok(event_id)
1540 /// .expect(1)
1541 /// .mount()
1542 /// .await;
1543 ///
1544 /// let response_not_mocked = room.send_state_event(RoomCreateEventContent::new_v11()).await;
1545 /// // The `m.room.reaction` event type should not be mocked by the server.
1546 /// assert!(response_not_mocked.is_err());
1547 ///
1548 /// let response = room.send_state_event(RoomPowerLevelsEventContent::new()).await?;
1549 /// // The `m.room.message` event type should be mocked by the server.
1550 /// assert_eq!(
1551 /// event_id, response.event_id,
1552 /// "The event ID we mocked should match the one we received when we sent the event"
1553 /// );
1554 ///
1555 /// # anyhow::Ok(()) });
1556 /// ```
1557 pub fn for_type(mut self, event_type: StateEventType) -> Self {
1558 self.endpoint.event_type = Some(event_type);
1559 // Note: we may have already defined a path, but this one ought to be more
1560 // specialized (unless for_key/for_type were called multiple times).
1561 Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
1562 }
1563
1564 /// Ensures the event was sent as a delayed event.
1565 ///
1566 /// Note: works with *any* room.
1567 ///
1568 /// # Examples
1569 ///
1570 /// see also [`MatrixMockServer::mock_room_send`] for more context.
1571 ///
1572 /// ```
1573 /// # tokio_test::block_on(async {
1574 /// use matrix_sdk::{
1575 /// ruma::{
1576 /// api::client::delayed_events::{delayed_state_event, DelayParameters},
1577 /// events::{room::create::RoomCreateEventContent, AnyStateEventContent},
1578 /// room_id,
1579 /// time::Duration,
1580 /// },
1581 /// test_utils::mocks::MatrixMockServer,
1582 /// };
1583 /// use wiremock::ResponseTemplate;
1584 /// use serde_json::json;
1585 ///
1586 /// let mock_server = MatrixMockServer::new().await;
1587 /// let client = mock_server.client_builder().build().await;
1588 ///
1589 /// mock_server.mock_room_state_encryption().plain().mount().await;
1590 ///
1591 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
1592 ///
1593 /// mock_server
1594 /// .mock_room_send_state()
1595 /// .with_delay(Duration::from_millis(500))
1596 /// .respond_with(ResponseTemplate::new(200).set_body_json(json!({"delay_id":"$some_id"})))
1597 /// .mock_once()
1598 /// .mount()
1599 /// .await;
1600 ///
1601 /// let response_not_mocked = room.send_state_event(RoomCreateEventContent::new_v11()).await;
1602 /// // A non delayed event should not be mocked by the server.
1603 /// assert!(response_not_mocked.is_err());
1604 ///
1605 /// let r = delayed_state_event::unstable::Request::new(
1606 /// room.room_id().to_owned(),
1607 /// "".to_owned(),
1608 /// DelayParameters::Timeout { timeout: Duration::from_millis(500) },
1609 /// &AnyStateEventContent::RoomCreate(RoomCreateEventContent::new_v11()),
1610 /// )
1611 /// .unwrap();
1612 /// let response = room.client().send(r).await.unwrap();
1613 /// // The delayed `m.room.message` event type should be mocked by the server.
1614 /// assert_eq!("$some_id", response.delay_id);
1615 ///
1616 /// # anyhow::Ok(()) });
1617 /// ```
1618 pub fn with_delay(self, delay: Duration) -> Self {
1619 Self {
1620 mock: self
1621 .mock
1622 .and(query_param("org.matrix.msc4140.delay", delay.as_millis().to_string())),
1623 ..self
1624 }
1625 }
1626
1627 ///
1628 /// ```
1629 /// # tokio_test::block_on(async {
1630 /// use matrix_sdk::{
1631 /// ruma::{
1632 /// event_id,
1633 /// events::{call::member::CallMemberEventContent, AnyStateEventContent},
1634 /// room_id,
1635 /// },
1636 /// test_utils::mocks::MatrixMockServer,
1637 /// };
1638 ///
1639 /// let mock_server = MatrixMockServer::new().await;
1640 /// let client = mock_server.client_builder().build().await;
1641 ///
1642 /// mock_server.mock_room_state_encryption().plain().mount().await;
1643 ///
1644 /// let room = mock_server.sync_joined_room(&client, room_id!("!room_id:localhost")).await;
1645 ///
1646 /// let event_id = event_id!("$some_id");
1647 ///
1648 /// mock_server
1649 /// .mock_room_send_state()
1650 /// .for_key("my_key".to_owned())
1651 /// .ok(event_id)
1652 /// .expect(1)
1653 /// .mount()
1654 /// .await;
1655 ///
1656 /// let response_not_mocked = room
1657 /// .send_state_event_for_key(
1658 /// "",
1659 /// AnyStateEventContent::CallMember(CallMemberEventContent::new_empty(None)),
1660 /// )
1661 /// .await;
1662 /// // The `m.room.reaction` event type should not be mocked by the server.
1663 /// assert!(response_not_mocked.is_err());
1664 ///
1665 /// let response = room
1666 /// .send_state_event_for_key(
1667 /// "my_key",
1668 /// AnyStateEventContent::CallMember(CallMemberEventContent::new_empty(None)),
1669 /// )
1670 /// .await
1671 /// .unwrap();
1672 ///
1673 /// // The `m.room.message` event type should be mocked by the server.
1674 /// assert_eq!(
1675 /// event_id, response.event_id,
1676 /// "The event ID we mocked should match the one we received when we sent the event"
1677 /// );
1678 /// # anyhow::Ok(()) });
1679 /// ```
1680 pub fn for_key(mut self, state_key: String) -> Self {
1681 self.endpoint.state_key = Some(state_key);
1682 // Note: we may have already defined a path, but this one ought to be more
1683 // specialized (unless for_key/for_type were called multiple times).
1684 Self { mock: self.mock.and(path_regex(Self::generate_path_regexp(&self.endpoint))), ..self }
1685 }
1686
1687 /// Returns a send endpoint that emulates success, i.e. the event has been
1688 /// sent with the given event id.
1689 ///
1690 /// # Examples
1691 /// ```
1692 /// # tokio_test::block_on(async {
1693 /// use matrix_sdk::{ruma::{room_id, event_id}, test_utils::mocks::MatrixMockServer};
1694 /// use serde_json::json;
1695 ///
1696 /// let mock_server = MatrixMockServer::new().await;
1697 /// let client = mock_server.client_builder().build().await;
1698 ///
1699 /// mock_server.mock_room_state_encryption().plain().mount().await;
1700 ///
1701 /// let room = mock_server
1702 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1703 /// .await;
1704 ///
1705 /// let event_id = event_id!("$some_id");
1706 /// let send_guard = mock_server
1707 /// .mock_room_send_state()
1708 /// .ok(event_id)
1709 /// .expect(1)
1710 /// .mount_as_scoped()
1711 /// .await;
1712 ///
1713 /// let response = room.send_state_event_raw("m.room.message", "my_key", json!({ "body": "Hello world" })).await?;
1714 ///
1715 /// assert_eq!(
1716 /// event_id,
1717 /// response.event_id,
1718 /// "The event ID we mocked should match the one we received when we sent the event"
1719 /// );
1720 /// # anyhow::Ok(()) });
1721 /// ```
1722 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
1723 self.ok_with_event_id(returned_event_id.into())
1724 }
1725}
1726
1727/// A prebuilt mock for running sync v2.
1728pub struct SyncEndpoint {
1729 sync_response_builder: Arc<Mutex<SyncResponseBuilder>>,
1730}
1731
1732impl MockEndpoint<'_, SyncEndpoint> {
1733 /// Temporarily mocks the sync with the given endpoint and runs a client
1734 /// sync with it.
1735 ///
1736 /// After calling this function, the sync endpoint isn't mocked anymore.
1737 ///
1738 /// # Examples
1739 ///
1740 /// ```
1741 /// # tokio_test::block_on(async {
1742 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
1743 /// use matrix_sdk_test::JoinedRoomBuilder;
1744 ///
1745 /// // First create the mock server and client pair.
1746 /// let mock_server = MatrixMockServer::new().await;
1747 /// let client = mock_server.client_builder().build().await;
1748 /// let room_id = room_id!("!room_id:localhost");
1749 ///
1750 /// // Let's emulate what `MatrixMockServer::sync_joined_room()` does.
1751 /// mock_server
1752 /// .mock_sync()
1753 /// .ok_and_run(&client, |builder| {
1754 /// builder.add_joined_room(JoinedRoomBuilder::new(room_id));
1755 /// })
1756 /// .await;
1757 ///
1758 /// let room = client
1759 /// .get_room(room_id)
1760 /// .expect("The room should be available after we mocked the sync");
1761 /// # anyhow::Ok(()) });
1762 /// ```
1763 pub async fn ok_and_run<F: FnOnce(&mut SyncResponseBuilder)>(self, client: &Client, func: F) {
1764 let json_response = {
1765 let mut builder = self.endpoint.sync_response_builder.lock().unwrap();
1766 func(&mut builder);
1767 builder.build_json_sync_response()
1768 };
1769
1770 let _scope = self
1771 .mock
1772 .respond_with(ResponseTemplate::new(200).set_body_json(json_response))
1773 .mount_as_scoped(self.server)
1774 .await;
1775
1776 let _response = client.sync_once(Default::default()).await.unwrap();
1777 }
1778}
1779
1780/// A prebuilt mock for reading the encryption state of a room.
1781pub struct EncryptionStateEndpoint;
1782
1783impl<'a> MockEndpoint<'a, EncryptionStateEndpoint> {
1784 /// Marks the room as encrypted.
1785 ///
1786 /// # Examples
1787 ///
1788 /// ```
1789 /// # tokio_test::block_on(async {
1790 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
1791 ///
1792 /// let mock_server = MatrixMockServer::new().await;
1793 /// let client = mock_server.client_builder().build().await;
1794 ///
1795 /// mock_server.mock_room_state_encryption().encrypted().mount().await;
1796 ///
1797 /// let room = mock_server
1798 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1799 /// .await;
1800 ///
1801 /// assert!(
1802 /// room.is_encrypted().await?,
1803 /// "The room should be marked as encrypted."
1804 /// );
1805 /// # anyhow::Ok(()) });
1806 /// ```
1807 pub fn encrypted(self) -> MatrixMock<'a> {
1808 let mock = self.mock.respond_with(
1809 ResponseTemplate::new(200).set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT),
1810 );
1811 MatrixMock { mock, server: self.server }
1812 }
1813
1814 /// Marks the room as not encrypted.
1815 ///
1816 /// # Examples
1817 ///
1818 /// ```
1819 /// # tokio_test::block_on(async {
1820 /// use matrix_sdk::{ruma::room_id, test_utils::mocks::MatrixMockServer};
1821 ///
1822 /// let mock_server = MatrixMockServer::new().await;
1823 /// let client = mock_server.client_builder().build().await;
1824 ///
1825 /// mock_server.mock_room_state_encryption().plain().mount().await;
1826 ///
1827 /// let room = mock_server
1828 /// .sync_joined_room(&client, room_id!("!room_id:localhost"))
1829 /// .await;
1830 ///
1831 /// assert!(
1832 /// !room.is_encrypted().await?,
1833 /// "The room should not be marked as encrypted."
1834 /// );
1835 /// # anyhow::Ok(()) });
1836 /// ```
1837 pub fn plain(self) -> MatrixMock<'a> {
1838 let mock = self
1839 .mock
1840 .respond_with(ResponseTemplate::new(404).set_body_json(&*test_json::NOT_FOUND));
1841 MatrixMock { mock, server: self.server }
1842 }
1843}
1844
1845/// A prebuilt mock for setting the encryption state of a room.
1846pub struct SetEncryptionStateEndpoint;
1847
1848impl<'a> MockEndpoint<'a, SetEncryptionStateEndpoint> {
1849 /// Returns a mock for a successful setting of the encryption state event.
1850 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
1851 self.ok_with_event_id(returned_event_id.into())
1852 }
1853}
1854
1855/// A prebuilt mock for redacting an event in a room.
1856pub struct RoomRedactEndpoint;
1857
1858impl<'a> MockEndpoint<'a, RoomRedactEndpoint> {
1859 /// Returns a redact endpoint that emulates success, i.e. the redaction
1860 /// event has been sent with the given event id.
1861 pub fn ok(self, returned_event_id: impl Into<OwnedEventId>) -> MatrixMock<'a> {
1862 self.ok_with_event_id(returned_event_id.into())
1863 }
1864}
1865
1866/// A prebuilt mock for getting a single event in a room.
1867pub struct RoomEventEndpoint {
1868 room: Option<OwnedRoomId>,
1869 match_event_id: bool,
1870}
1871
1872impl<'a> MockEndpoint<'a, RoomEventEndpoint> {
1873 /// Limits the scope of this mock to a specific room.
1874 pub fn room(mut self, room: impl Into<OwnedRoomId>) -> Self {
1875 self.endpoint.room = Some(room.into());
1876 self
1877 }
1878
1879 /// Whether the mock checks for the event id from the event.
1880 pub fn match_event_id(mut self) -> Self {
1881 self.endpoint.match_event_id = true;
1882 self
1883 }
1884
1885 /// Returns a redact endpoint that emulates success, i.e. the redaction
1886 /// event has been sent with the given event id.
1887 pub fn ok(self, event: TimelineEvent) -> MatrixMock<'a> {
1888 let event_path = if self.endpoint.match_event_id {
1889 let event_id = event.kind.event_id().expect("an event id is required");
1890 // The event id should begin with `$`, which would be taken as the end of the
1891 // regex so we need to escape it
1892 event_id.as_str().replace("$", "\\$")
1893 } else {
1894 // Event is at the end, so no need to add anything.
1895 "".to_owned()
1896 };
1897
1898 let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string());
1899
1900 let mock = self
1901 .mock
1902 .and(path_regex(format!(r"^/_matrix/client/v3/rooms/{room_path}/event/{event_path}")))
1903 .respond_with(ResponseTemplate::new(200).set_body_json(event.into_raw().json()));
1904 MatrixMock { server: self.server, mock }
1905 }
1906}
1907
1908/// A prebuilt mock for the `/messages` endpoint.
1909pub struct RoomMessagesEndpoint;
1910
1911/// A prebuilt mock for getting a room messages in a room.
1912impl<'a> MockEndpoint<'a, RoomMessagesEndpoint> {
1913 /// Expects an optional limit to be set on the request.
1914 pub fn limit(self, limit: u32) -> Self {
1915 Self { mock: self.mock.and(query_param("limit", limit.to_string())), ..self }
1916 }
1917
1918 /// Expects an optional `from` to be set on the request.
1919 pub fn from(self, from: &str) -> Self {
1920 Self { mock: self.mock.and(query_param("from", from)), ..self }
1921 }
1922
1923 /// Returns a messages endpoint that emulates success, i.e. the messages
1924 /// provided as `response` could be retrieved.
1925 ///
1926 /// Note: pass `chunk` in the correct order: topological for forward
1927 /// pagination, reverse topological for backwards pagination.
1928 pub fn ok(self, response: RoomMessagesResponseTemplate) -> MatrixMock<'a> {
1929 let mut template = ResponseTemplate::new(200).set_body_json(json!({
1930 "start": response.start,
1931 "end": response.end,
1932 "chunk": response.chunk,
1933 "state": response.state,
1934 }));
1935
1936 if let Some(delay) = response.delay {
1937 template = template.set_delay(delay);
1938 }
1939
1940 let mock = self.mock.respond_with(template);
1941 MatrixMock { server: self.server, mock }
1942 }
1943}
1944
1945/// A response to a [`RoomMessagesEndpoint`] query.
1946pub struct RoomMessagesResponseTemplate {
1947 /// The start token for this /messages query.
1948 pub start: String,
1949 /// The end token for this /messages query (previous batch for back
1950 /// paginations, next batch for forward paginations).
1951 pub end: Option<String>,
1952 /// The set of timeline events returned by this query.
1953 pub chunk: Vec<Raw<AnyTimelineEvent>>,
1954 /// The set of state events returned by this query.
1955 pub state: Vec<Raw<AnyStateEvent>>,
1956 /// Optional delay to respond to the query.
1957 pub delay: Option<Duration>,
1958}
1959
1960impl RoomMessagesResponseTemplate {
1961 /// Fill the events returned as part of this response.
1962 pub fn events(mut self, chunk: Vec<impl Into<Raw<AnyTimelineEvent>>>) -> Self {
1963 self.chunk = chunk.into_iter().map(Into::into).collect();
1964 self
1965 }
1966
1967 /// Fill the end token.
1968 pub fn end_token(mut self, token: impl Into<String>) -> Self {
1969 self.end = Some(token.into());
1970 self
1971 }
1972
1973 /// Respond with a given delay to the query.
1974 pub fn delayed(mut self, delay: Duration) -> Self {
1975 self.delay = Some(delay);
1976 self
1977 }
1978}
1979
1980impl Default for RoomMessagesResponseTemplate {
1981 fn default() -> Self {
1982 Self {
1983 start: "start-token-unused".to_owned(),
1984 end: Default::default(),
1985 chunk: Default::default(),
1986 state: Default::default(),
1987 delay: None,
1988 }
1989 }
1990}
1991
1992/// A prebuilt mock for uploading media.
1993pub struct UploadEndpoint;
1994
1995impl<'a> MockEndpoint<'a, UploadEndpoint> {
1996 /// Expect that the content type matches what's given here.
1997 pub fn expect_mime_type(self, content_type: &str) -> Self {
1998 Self { mock: self.mock.and(header("content-type", content_type)), ..self }
1999 }
2000
2001 /// Returns a redact endpoint that emulates success, i.e. the redaction
2002 /// event has been sent with the given event id.
2003 pub fn ok(self, mxc_id: &MxcUri) -> MatrixMock<'a> {
2004 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2005 "content_uri": mxc_id
2006 })));
2007 MatrixMock { server: self.server, mock }
2008 }
2009}
2010
2011/// A prebuilt mock for resolving a room alias.
2012pub struct ResolveRoomAliasEndpoint;
2013
2014impl<'a> MockEndpoint<'a, ResolveRoomAliasEndpoint> {
2015 /// Sets up the endpoint to only intercept requests for the given room
2016 /// alias.
2017 pub fn for_alias(self, alias: impl Into<String>) -> Self {
2018 let alias = alias.into();
2019 Self {
2020 mock: self.mock.and(path_regex(format!(
2021 r"^/_matrix/client/v3/directory/room/{}",
2022 percent_encoded_path(&alias)
2023 ))),
2024 ..self
2025 }
2026 }
2027
2028 /// Returns a data endpoint with a resolved room alias.
2029 pub fn ok(self, room_id: &str, servers: Vec<String>) -> MatrixMock<'a> {
2030 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2031 "room_id": room_id,
2032 "servers": servers,
2033 })));
2034 MatrixMock { server: self.server, mock }
2035 }
2036
2037 /// Returns a data endpoint for a room alias that does not exit.
2038 pub fn not_found(self) -> MatrixMock<'a> {
2039 let mock = self.mock.respond_with(ResponseTemplate::new(404).set_body_json(json!({
2040 "errcode": "M_NOT_FOUND",
2041 "error": "Room alias not found."
2042 })));
2043 MatrixMock { server: self.server, mock }
2044 }
2045}
2046
2047/// A prebuilt mock for creating a room alias.
2048pub struct CreateRoomAliasEndpoint;
2049
2050impl<'a> MockEndpoint<'a, CreateRoomAliasEndpoint> {
2051 /// Returns a data endpoint for creating a room alias.
2052 pub fn ok(self) -> MatrixMock<'a> {
2053 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2054 MatrixMock { server: self.server, mock }
2055 }
2056}
2057
2058/// A prebuilt mock for removing a room alias.
2059pub struct RemoveRoomAliasEndpoint;
2060
2061impl<'a> MockEndpoint<'a, RemoveRoomAliasEndpoint> {
2062 /// Returns a data endpoint for removing a room alias.
2063 pub fn ok(self) -> MatrixMock<'a> {
2064 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2065 MatrixMock { server: self.server, mock }
2066 }
2067}
2068
2069/// A prebuilt mock for paginating the public room list.
2070pub struct PublicRoomsEndpoint;
2071
2072impl<'a> MockEndpoint<'a, PublicRoomsEndpoint> {
2073 /// Returns a data endpoint for paginating the public room list.
2074 pub fn ok(
2075 self,
2076 chunk: Vec<PublicRoomsChunk>,
2077 next_batch: Option<String>,
2078 prev_batch: Option<String>,
2079 total_room_count_estimate: Option<u64>,
2080 ) -> MatrixMock<'a> {
2081 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2082 "chunk": chunk,
2083 "next_batch": next_batch,
2084 "prev_batch": prev_batch,
2085 "total_room_count_estimate": total_room_count_estimate,
2086 })));
2087 MatrixMock { server: self.server, mock }
2088 }
2089
2090 /// Returns a data endpoint for paginating the public room list with several
2091 /// `via` params.
2092 ///
2093 /// Each `via` param must be in the `server_map` parameter, otherwise it'll
2094 /// fail.
2095 pub fn ok_with_via_params(
2096 self,
2097 server_map: BTreeMap<OwnedServerName, Vec<PublicRoomsChunk>>,
2098 ) -> MatrixMock<'a> {
2099 let mock = self.mock.respond_with(move |req: &Request| {
2100 #[derive(Deserialize)]
2101 struct PartialRequest {
2102 server: Option<OwnedServerName>,
2103 }
2104
2105 let (_, server) = req
2106 .url
2107 .query_pairs()
2108 .into_iter()
2109 .find(|(key, _)| key == "server")
2110 .expect("Server param not found in request URL");
2111 let server = ServerName::parse(server).expect("Couldn't parse server name");
2112 let chunk = server_map.get(&server).expect("Chunk for the server param not found");
2113 ResponseTemplate::new(200).set_body_json(json!({
2114 "chunk": chunk,
2115 "total_room_count_estimate": chunk.len(),
2116 }))
2117 });
2118 MatrixMock { server: self.server, mock }
2119 }
2120}
2121
2122/// A prebuilt mock for getting the room's visibility in the room directory.
2123pub struct GetRoomVisibilityEndpoint;
2124
2125impl<'a> MockEndpoint<'a, GetRoomVisibilityEndpoint> {
2126 /// Returns an endpoint that get the room's public visibility.
2127 pub fn ok(self, visibility: Visibility) -> MatrixMock<'a> {
2128 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2129 "visibility": visibility,
2130 })));
2131 MatrixMock { server: self.server, mock }
2132 }
2133}
2134
2135/// A prebuilt mock for setting the room's visibility in the room directory.
2136pub struct SetRoomVisibilityEndpoint;
2137
2138impl<'a> MockEndpoint<'a, SetRoomVisibilityEndpoint> {
2139 /// Returns an endpoint that updates the room's visibility.
2140 pub fn ok(self) -> MatrixMock<'a> {
2141 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2142 MatrixMock { server: self.server, mock }
2143 }
2144}
2145
2146/// A prebuilt mock for `GET room_keys/version`: storage ("backup") of room
2147/// keys.
2148pub struct RoomKeysVersionEndpoint;
2149
2150impl<'a> MockEndpoint<'a, RoomKeysVersionEndpoint> {
2151 /// Returns an endpoint that says there is a single room keys backup
2152 pub fn exists(self) -> MatrixMock<'a> {
2153 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2154 "algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
2155 "auth_data": {
2156 "public_key": "abcdefg",
2157 "signatures": {},
2158 },
2159 "count": 42,
2160 "etag": "anopaquestring",
2161 "version": "1",
2162 })));
2163 MatrixMock { server: self.server, mock }
2164 }
2165
2166 /// Returns an endpoint that says there is no room keys backup
2167 pub fn none(self) -> MatrixMock<'a> {
2168 let mock = self.mock.respond_with(ResponseTemplate::new(404).set_body_json(json!({
2169 "errcode": "M_NOT_FOUND",
2170 "error": "No current backup version"
2171 })));
2172 MatrixMock { server: self.server, mock }
2173 }
2174
2175 /// Returns an endpoint that 429 errors when we get it
2176 pub fn error429(self) -> MatrixMock<'a> {
2177 let mock = self.mock.respond_with(ResponseTemplate::new(429).set_body_json(json!({
2178 "errcode": "M_LIMIT_EXCEEDED",
2179 "error": "Too many requests",
2180 "retry_after_ms": 2000
2181 })));
2182 MatrixMock { server: self.server, mock }
2183 }
2184
2185 /// Returns an endpoint that 404 errors when we get it
2186 pub fn error404(self) -> MatrixMock<'a> {
2187 let mock = self.mock.respond_with(ResponseTemplate::new(404));
2188 MatrixMock { server: self.server, mock }
2189 }
2190}
2191
2192/// A prebuilt mock for `POST room_keys/version`: adding room key backups.
2193pub struct AddRoomKeysVersionEndpoint;
2194
2195impl<'a> MockEndpoint<'a, AddRoomKeysVersionEndpoint> {
2196 /// Returns an endpoint that may be used to add room key backups
2197 pub fn ok(self) -> MatrixMock<'a> {
2198 let mock = self
2199 .mock
2200 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2201 "version": "1"
2202 })))
2203 .named("POST for the backup creation");
2204 MatrixMock { server: self.server, mock }
2205 }
2206}
2207
2208/// A prebuilt mock for `DELETE room_keys/version/xxx`: deleting room key
2209/// backups.
2210pub struct DeleteRoomKeysVersionEndpoint;
2211
2212impl<'a> MockEndpoint<'a, DeleteRoomKeysVersionEndpoint> {
2213 /// Returns an endpoint that allows deleting room key backups
2214 pub fn ok(self) -> MatrixMock<'a> {
2215 let mock = self
2216 .mock
2217 .respond_with(ResponseTemplate::new(200).set_body_json(json!({})))
2218 .named("DELETE for the backup deletion");
2219 MatrixMock { server: self.server, mock }
2220 }
2221}
2222
2223/// A prebuilt mock for `GET /members` request.
2224pub struct GetRoomMembersEndpoint;
2225
2226impl<'a> MockEndpoint<'a, GetRoomMembersEndpoint> {
2227 /// Returns a successful get members request with a list of members.
2228 pub fn ok(self, members: Vec<Raw<RoomMemberEvent>>) -> MatrixMock<'a> {
2229 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2230 "chunk": members,
2231 })));
2232 MatrixMock { server: self.server, mock }
2233 }
2234}
2235
2236/// A prebuilt mock for `POST /invite` request.
2237pub struct InviteUserByIdEndpoint;
2238
2239impl<'a> MockEndpoint<'a, InviteUserByIdEndpoint> {
2240 /// Returns a successful invite user by id request.
2241 pub fn ok(self) -> MatrixMock<'a> {
2242 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2243 MatrixMock { server: self.server, mock }
2244 }
2245}
2246
2247/// A prebuilt mock for `POST /kick` request.
2248pub struct KickUserEndpoint;
2249
2250impl<'a> MockEndpoint<'a, KickUserEndpoint> {
2251 /// Returns a successful kick user request.
2252 pub fn ok(self) -> MatrixMock<'a> {
2253 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2254 MatrixMock { server: self.server, mock }
2255 }
2256}
2257
2258/// A prebuilt mock for `POST /ban` request.
2259pub struct BanUserEndpoint;
2260
2261impl<'a> MockEndpoint<'a, BanUserEndpoint> {
2262 /// Returns a successful ban user request.
2263 pub fn ok(self) -> MatrixMock<'a> {
2264 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));
2265 MatrixMock { server: self.server, mock }
2266 }
2267}
2268
2269/// A prebuilt mock for `GET /versions` request.
2270pub struct VersionsEndpoint;
2271
2272impl<'a> MockEndpoint<'a, VersionsEndpoint> {
2273 /// Returns a successful `/_matrix/client/versions` request.
2274 ///
2275 /// The response will return some commonly supported versions.
2276 pub fn ok(self) -> MatrixMock<'a> {
2277 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2278 "unstable_features": {
2279 },
2280 "versions": [
2281 "r0.0.1",
2282 "r0.2.0",
2283 "r0.3.0",
2284 "r0.4.0",
2285 "r0.5.0",
2286 "r0.6.0",
2287 "r0.6.1",
2288 "v1.1",
2289 "v1.2",
2290 "v1.3",
2291 "v1.4",
2292 "v1.5",
2293 "v1.6",
2294 "v1.7",
2295 "v1.8",
2296 "v1.9",
2297 "v1.10",
2298 "v1.11"
2299 ]
2300 })));
2301
2302 MatrixMock { server: self.server, mock }
2303 }
2304}
2305
2306/// A prebuilt mock for the room summary endpoint.
2307pub struct RoomSummaryEndpoint;
2308
2309impl<'a> MockEndpoint<'a, RoomSummaryEndpoint> {
2310 /// Returns a successful response with some default data for the given room
2311 /// id.
2312 pub fn ok(self, room_id: &RoomId) -> MatrixMock<'a> {
2313 let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({
2314 "room_id": room_id,
2315 "guest_can_join": true,
2316 "num_joined_members": 1,
2317 "world_readable": true,
2318 "join_rule": "public",
2319 })));
2320 MatrixMock { server: self.server, mock }
2321 }
2322}
2323
2324/// A prebuilt mock to set a room's pinned events.
2325pub struct SetRoomPinnedEventsEndpoint;
2326
2327impl<'a> MockEndpoint<'a, SetRoomPinnedEventsEndpoint> {
2328 /// Returns a successful response with a given event id.
2329 /// id.
2330 pub fn ok(self, event_id: OwnedEventId) -> MatrixMock<'a> {
2331 self.ok_with_event_id(event_id)
2332 }
2333
2334 /// Returns an error response with a generic error code indicating the
2335 /// client is not authorized to set pinned events.
2336 pub fn unauthorized(self) -> MatrixMock<'a> {
2337 let mock = self.mock.respond_with(ResponseTemplate::new(400));
2338 MatrixMock { server: self.server, mock }
2339 }
2340}