1//! Testing utilities - DO NOT USE IN PRODUCTION.
23#![allow(dead_code)]
45use assert_matches2::assert_let;
6use matrix_sdk_base::{deserialized_responses::TimelineEvent, store::RoomLoadSettings};
7use ruma::{
8 api::MatrixVersion,
9 events::{room::message::MessageType, AnySyncMessageLikeEvent, AnySyncTimelineEvent},
10};
11use url::Url;
1213pub mod client;
14#[cfg(not(target_arch = "wasm32"))]
15pub mod mocks;
1617use self::client::mock_matrix_session;
18use crate::{config::RequestConfig, Client, ClientBuilder};
1920/// Checks that an event is a message-like text event with the given text.
21#[track_caller]
22pub fn assert_event_matches_msg<E: Clone + Into<TimelineEvent>>(event: &E, expected: &str) {
23let event: TimelineEvent = event.clone().into();
24let event = event.raw().deserialize().unwrap();
25assert_let!(
26 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) = event
27 );
28let message = message.as_original().unwrap();
29assert_let!(MessageType::Text(text) = &message.content.msgtype);
30assert_eq!(text.body, expected);
31}
3233/// A [`ClientBuilder`] fit for testing, using the given `homeserver_url` (or
34/// localhost:1234).
35pub fn test_client_builder(homeserver_url: Option<String>) -> ClientBuilder {
36let homeserver = homeserver_url
37 .map(|url| Url::try_from(url.as_str()).unwrap())
38 .unwrap_or_else(|| Url::try_from("http://localhost:1234").unwrap());
39 Client::builder().homeserver_url(homeserver).server_versions([MatrixVersion::V1_0])
40}
4142/// A [`Client`] using the given `homeserver_url` (or localhost:1234), that will
43/// never retry any failed requests.
44pub async fn no_retry_test_client(homeserver_url: Option<String>) -> Client {
45 test_client_builder(homeserver_url)
46 .request_config(RequestConfig::new().disable_retry())
47 .build()
48 .await
49.unwrap()
50}
5152/// Restore the common (Matrix-auth) user session for a client.
53pub async fn set_client_session(client: &Client) {
54 client
55 .matrix_auth()
56 .restore_session(mock_matrix_session(), RoomLoadSettings::default())
57 .await
58.unwrap();
59}
6061/// A [`Client`] using the given `homeserver_url` (or localhost:1234), that will
62/// never retry any failed requests, and already logged in with an hardcoded
63/// Matrix authentication session (the user id and device id are hardcoded too).
64pub async fn logged_in_client(homeserver_url: Option<String>) -> Client {
65let client = no_retry_test_client(homeserver_url).await;
66 set_client_session(&client).await;
67 client
68}
6970/// Like [`test_client_builder`], but with a mocked server too.
71#[cfg(not(target_arch = "wasm32"))]
72pub async fn test_client_builder_with_server() -> (ClientBuilder, wiremock::MockServer) {
73let server = wiremock::MockServer::start().await;
74let builder = test_client_builder(Some(server.uri()));
75 (builder, server)
76}
7778/// Like [`no_retry_test_client`], but with a mocked server too.
79#[cfg(not(target_arch = "wasm32"))]
80pub async fn no_retry_test_client_with_server() -> (Client, wiremock::MockServer) {
81let server = wiremock::MockServer::start().await;
82let client = no_retry_test_client(Some(server.uri().to_string())).await;
83 (client, server)
84}
8586/// Like [`logged_in_client`], but with a mocked server too.
87#[cfg(not(target_arch = "wasm32"))]
88pub async fn logged_in_client_with_server() -> (Client, wiremock::MockServer) {
89let server = wiremock::MockServer::start().await;
90let client = logged_in_client(Some(server.uri().to_string())).await;
91 (client, server)
92}
9394/// Asserts that the next item in a `Stream` is received within a given timeout.
95///
96/// This macro waits for the next item from an asynchronous `Stream` or, if no
97/// item is received within the specified timeout, the macro panics.
98///
99/// # Parameters
100///
101/// - `$stream`: The `Stream` or `Subscriber` to poll for the next item.
102/// - `$timeout_ms` (optional): The timeout in milliseconds to wait for the next
103/// item. Defaults to 500ms if not provided.
104///
105/// # Example
106///
107/// ```rust
108/// use futures_util::{stream, StreamExt};
109/// use matrix_sdk::assert_next_with_timeout;
110///
111/// # async {
112/// let mut stream = stream::iter(vec![1, 2, 3]);
113/// let next_item = assert_next_with_timeout!(stream, 1000); // Waits up to 1000ms
114/// assert_eq!(next_item, 1);
115///
116/// // The timeout can be omitted, in which case it defaults to 500 ms.
117/// let next_item = assert_next_with_timeout!(stream); // Waits up to 500ms
118/// assert_eq!(next_item, 2);
119/// # };
120/// ```
121#[macro_export]
122macro_rules! assert_next_with_timeout {
123 ($stream:expr) => {
124$crate::assert_next_with_timeout!($stream, 500)
125 };
126 ($stream:expr, $timeout_ms:expr) => {{
127// Needed for subscribers, as they won't use the StreamExt features
128#[allow(unused_imports)]
129use futures_util::StreamExt as _;
130 tokio::time::timeout(std::time::Duration::from_millis($timeout_ms), $stream.next())
131 .await
132.expect("Next event timed out")
133 .expect("No next event received")
134 }};
135}
136137/// Asserts the next item in a `Receiver` can be loaded in the given timeout in
138/// milliseconds.
139#[macro_export]
140macro_rules! assert_recv_with_timeout {
141 ($receiver:expr, $timeout_ms:expr) => {{
142 tokio::time::timeout(std::time::Duration::from_millis($timeout_ms), $receiver.recv())
143 .await
144.expect("Next event timed out")
145 .expect("No next event received")
146 }};
147}
148149/// Assert the next item in a `Stream` or `Subscriber` matches the provided
150/// pattern in the given timeout in milliseconds.
151///
152/// If no timeout is provided, a default `100ms` value will be used.
153#[macro_export]
154macro_rules! assert_next_matches_with_timeout {
155 ($stream:expr, $pat:pat) => {
156$crate::assert_next_matches_with_timeout!($stream, $pat => {})
157 };
158 ($stream:expr, $pat:pat => $arm:expr) => {
159$crate::assert_next_matches_with_timeout!($stream, 100, $pat => $arm)
160 };
161 ($stream:expr, $timeout_ms:expr, $pat:pat => $arm:expr) => {
162match $crate::assert_next_with_timeout!(&mut $stream, $timeout_ms) {
163$pat => $arm,
164 val => {
165::core::panic!(
166"assertion failed: `{:?}` does not match `{}`",
167 val, ::core::stringify!($pat)
168 );
169 }
170 }
171 };
172}
173174/// Asserts that the next item from an asynchronous stream is equal to the
175/// expected value, with an optional timeout and custom error message.
176///
177/// # Arguments
178///
179/// * `$stream` - The asynchronous stream to retrieve the next item from.
180/// * `$expected` - The expected value to assert against.
181/// * `$timeout ms` (optional) - A timeout in milliseconds (e.g., `200ms`).
182/// Defaults to `100ms`.
183/// * `$msg` (optional) - A formatted message string for assertion failure.
184///
185/// # Examples
186///
187/// ```
188/// # async {
189/// # use matrix_sdk::assert_next_eq_with_timeout;
190/// # use tokio_stream::StreamExt;
191///
192/// let mut stream = tokio_stream::iter(vec![42]);
193/// assert_next_eq_with_timeout!(stream, 42);
194///
195/// let mut stream = tokio_stream::iter(vec![42]);
196/// assert_next_eq_with_timeout!(stream, 42, 200 ms);
197///
198/// let mut stream = tokio_stream::iter(vec![42]);
199/// assert_next_eq_with_timeout!(
200/// stream,
201/// 42,
202/// "Expected 42 but got something else"
203/// );
204///
205/// let mut stream = tokio_stream::iter(vec![42]);
206/// assert_next_eq_with_timeout!(stream, 42, 200 ms, "Expected 42 within 200ms");
207/// # };
208/// ```
209#[macro_export]
210macro_rules! assert_next_eq_with_timeout {
211 ($stream:expr, $expected:expr) => {
212$crate::assert_next_eq_with_timeout_impl!($stream, $expected, std::time::Duration::from_millis(100));
213 };
214 ($stream:expr, $expected:expr, $timeout:literal ms) => {
215$crate::assert_next_eq_with_timeout_impl!($stream, $expected, std::time::Duration::from_millis($timeout));
216 };
217 ($stream:expr, $expected:expr, $timeout:literal ms, $($msg:tt)*) => {
218$crate::assert_next_eq_with_timeout_impl!($stream, $expected, std::time::Duration::from_millis($timeout), $($msg)*);
219 };
220 ($stream:expr, $expected:expr, $($msg:tt)*) => {
221$crate::assert_next_eq_with_timeout_impl!($stream, $expected, std::time::Duration::from_millis(100), $($msg)*);
222 };
223}
224225#[doc(hidden)]
226#[macro_export]
227macro_rules! assert_next_eq_with_timeout_impl {
228 ($stream:expr, $expected:expr, $timeout:expr) => {
229let next_value = tokio::time::timeout(
230$timeout,
231$stream.next()
232 )
233 .await
234.expect("We should be able to get the next value out of the stream by now")
235 .expect("The stream should have given us a new value instead of None");
236237assert_eq!(next_value, $expected);
238 };
239 ($stream:expr, $expected:expr, $timeout:expr, $($msg:tt)*) => {{
240let next_value = tokio::time::timeout(
241$timeout,
242 futures_util::StreamExt::next(&mut $stream)
243 )
244 .await
245.expect("We should be able to get the next value out of the stream by now")
246 .expect("The stream should have given us a new value instead of None");
247248assert_eq!(next_value, $expected, $($msg)*);
249 }};
250}
251252/// Like `assert_let`, but with the possibility to add an optional timeout.
253///
254/// If not provided, a timeout value of 100 milliseconds is used.
255#[macro_export]
256macro_rules! assert_let_timeout {
257 ($timeout:expr, $pat:pat = $future:expr) => {
258assert_matches2::assert_let!(Ok($pat) = tokio::time::timeout($timeout, $future).await);
259 };
260261 ($pat:pat = $future:expr) => {
262assert_let_timeout!(std::time::Duration::from_millis(100), $pat = $future);
263 };
264}