| 1 | use std::{fmt, sync::LazyLock}; |
| 2 | |
| 3 | use http::Response; |
| 4 | pub use matrix_sdk_test_macros::async_test; |
| 5 | use ruma::{ |
| 6 | RoomId, UserId, |
| 7 | api::{IncomingResponse, OutgoingResponse}, |
| 8 | room_id, |
| 9 | serde::{Base64, base64::UrlSafe}, |
| 10 | user_id, |
| 11 | }; |
| 12 | use sha2::{Digest, Sha256}; |
| 13 | |
| 14 | /// Create a `Raw<AnyMessageLikeEventContent>` from arbitrary JSON. |
| 15 | /// |
| 16 | /// Forwards all arguments to [`serde_json::json`]. |
| 17 | #[macro_export] |
| 18 | macro_rules! message_like_event_content { |
| 19 | ($( $tt:tt )*) => { |
| 20 | ::ruma::serde::Raw::new(&::serde_json::json!( $($tt)* )) |
| 21 | .unwrap() |
| 22 | .cast_unchecked::<::ruma::events::AnyMessageLikeEventContent>() |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | /// Create a `Raw<AnyTimelineEvent>` from arbitrary JSON. |
| 27 | /// |
| 28 | /// Forwards all arguments to [`serde_json::json`]. |
| 29 | #[macro_export] |
| 30 | macro_rules! timeline_event { |
| 31 | ($( $tt:tt )*) => { |
| 32 | ::ruma::serde::Raw::new(&::serde_json::json!( $($tt)* )) |
| 33 | .unwrap() |
| 34 | .cast_unchecked::<::ruma::events::AnyTimelineEvent>() |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | /// Create a `Raw<AnySyncTimelineEvent>` from arbitrary JSON. |
| 39 | /// |
| 40 | /// Forwards all arguments to [`serde_json::json`]. |
| 41 | #[macro_export] |
| 42 | macro_rules! sync_timeline_event { |
| 43 | ($( $tt:tt )*) => { |
| 44 | ::ruma::serde::Raw::new(&::serde_json::json!( $($tt)* )) |
| 45 | .unwrap() |
| 46 | .cast_unchecked::<::ruma::events::AnySyncTimelineEvent>() |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | /// Create a `Raw<AnySyncStateEvent>` from arbitrary JSON. |
| 51 | /// |
| 52 | /// Forwards all arguments to [`serde_json::json`]. |
| 53 | #[macro_export] |
| 54 | macro_rules! sync_state_event { |
| 55 | ($( $tt:tt )*) => { |
| 56 | ::ruma::serde::Raw::new(&::serde_json::json!( $($tt)* )) |
| 57 | .unwrap() |
| 58 | .cast_unchecked::<::ruma::events::AnySyncStateEvent>() |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | /// Create a `Raw<AnyStrippedStateEvent>` from arbitrary JSON. |
| 63 | /// |
| 64 | /// Forwards all arguments to [`serde_json::json`]. |
| 65 | #[macro_export] |
| 66 | macro_rules! stripped_state_event { |
| 67 | ($( $tt:tt )*) => { |
| 68 | ::ruma::serde::Raw::new(&::serde_json::json!( $($tt)* )) |
| 69 | .unwrap() |
| 70 | .cast_unchecked::<::ruma::events::AnyStrippedStateEvent>() |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | #[cfg(not(target_family = "wasm"))] |
| 75 | pub mod mocks; |
| 76 | |
| 77 | pub mod event_factory; |
| 78 | pub mod notification_settings; |
| 79 | mod sync_builder; |
| 80 | pub mod test_json; |
| 81 | |
| 82 | pub use self::sync_builder::{ |
| 83 | InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder, |
| 84 | SyncResponseBuilder, bulk_room_members, |
| 85 | }; |
| 86 | |
| 87 | pub static ALICE: LazyLock<&UserId> = LazyLock::new(|| user_id!("@alice:server.name")); |
| 88 | pub static BOB: LazyLock<&UserId> = LazyLock::new(|| user_id!("@bob:other.server")); |
| 89 | pub static CAROL: LazyLock<&UserId> = LazyLock::new(|| user_id!("@carol:other.server")); |
| 90 | |
| 91 | /// The default room ID for tests. |
| 92 | pub static DEFAULT_TEST_ROOM_ID: LazyLock<&RoomId> = |
| 93 | LazyLock::new(|| room_id!("!SVkFJHzfwvuaIEawgC:localhost")); |
| 94 | |
| 95 | /// Build a typed Ruma [`IncomingResponse`] object from a json body. |
| 96 | pub fn ruma_response_from_json<ResponseType: IncomingResponse>( |
| 97 | json: &serde_json::Value, |
| 98 | ) -> ResponseType { |
| 99 | let json_bytes = serde_json::to_vec(json).expect("JSON-serialization of response value failed"); |
| 100 | let http_response = |
| 101 | Response::builder().status(200).body(json_bytes).expect("Failed to build HTTP response"); |
| 102 | ResponseType::try_from_http_response(http_response).expect("Can't parse the response json") |
| 103 | } |
| 104 | |
| 105 | /// Serialise a typed Ruma [`OutgoingResponse`] object to JSON. |
| 106 | pub fn ruma_response_to_json<ResponseType: OutgoingResponse>( |
| 107 | response: ResponseType, |
| 108 | ) -> serde_json::Value { |
| 109 | let http_response: Response<Vec<u8>> = |
| 110 | response.try_into_http_response().expect("Failed to build HTTP response"); |
| 111 | let json_bytes = http_response.into_body(); |
| 112 | serde_json::from_slice(&json_bytes).expect("Can't parse the response JSON") |
| 113 | } |
| 114 | |
| 115 | #[derive(Debug)] // required to be able to return TestResult from #[test] fns |
| 116 | pub enum TestError {} |
| 117 | |
| 118 | // If this was just `T: Debug`, it would conflict with |
| 119 | // the `impl From<T> for T` in `std`. |
| 120 | // |
| 121 | // Adding a dummy `Display` bound works around this. |
| 122 | impl<T: fmt::Display + fmt::Debug> From<T> for TestError { |
| 123 | #[track_caller] |
| 124 | fn from(value: T) -> Self { |
| 125 | panic!("error: {value:?}") |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | pub type TestResult = Result<(), TestError>; |
| 130 | |
| 131 | /// Compute the sha256 hash of the given bytes and convert it to unpadded |
| 132 | /// URL-safe base64. |
| 133 | /// |
| 134 | /// This matches the format of event IDs since room version 4, and of room IDs |
| 135 | /// since room version 12. |
| 136 | pub fn base64_sha256_hash(bytes: &[u8]) -> String { |
| 137 | Base64::<UrlSafe, _>::new(Sha256::digest(bytes)).encode() |
| 138 | } |