|
17 | 17 | //! tests.
|
18 | 18 | use std::{
|
19 | 19 | collections::BTreeMap,
|
| 20 | + future::Future, |
20 | 21 | sync::{atomic::Ordering, Arc, Mutex},
|
21 | 22 | };
|
22 | 23 |
|
| 24 | +use matrix_sdk_test::test_json; |
23 | 25 | use ruma::{
|
24 |
| - api::client::keys::upload_signatures::v3::SignedKeys, |
| 26 | + api::client::{ |
| 27 | + keys::upload_signatures::v3::SignedKeys, to_device::send_event_to_device::v3::Messages, |
| 28 | + }, |
25 | 29 | encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
|
26 | 30 | owned_device_id, owned_user_id,
|
27 | 31 | serde::Raw,
|
28 |
| - CrossSigningKeyId, DeviceId, OneTimeKeyAlgorithm, OwnedDeviceId, OwnedOneTimeKeyId, |
29 |
| - OwnedUserId, UserId, |
| 32 | + CrossSigningKeyId, DeviceId, MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OwnedDeviceId, |
| 33 | + OwnedOneTimeKeyId, OwnedUserId, UserId, |
30 | 34 | };
|
31 | 35 | use serde_json::json;
|
32 | 36 | use wiremock::{
|
33 | 37 | matchers::{method, path_regex},
|
34 |
| - Mock, Request, ResponseTemplate, |
| 38 | + Mock, MockGuard, Request, ResponseTemplate, |
35 | 39 | };
|
36 | 40 |
|
37 | 41 | use crate::{
|
| 42 | + crypto::types::events::room::encrypted::EncryptedToDeviceEvent, |
38 | 43 | test_utils::{
|
39 | 44 | client::MockClientBuilder,
|
40 | 45 | mocks::{Keys, MatrixMockServer},
|
@@ -178,6 +183,156 @@ impl MatrixMockServer {
|
178 | 183 | .mount(&self.server)
|
179 | 184 | .await;
|
180 | 185 | }
|
| 186 | + |
| 187 | + /// Creates a response handler for mocking encrypted to-device message |
| 188 | + /// requests. |
| 189 | + /// |
| 190 | + /// This function creates a response handler that captures encrypted |
| 191 | + /// to-device messages sent via the `/sendToDevice` endpoint. |
| 192 | + /// |
| 193 | + /// # Arguments |
| 194 | + /// |
| 195 | + /// * `sender` - The user ID of the message sender |
| 196 | + /// |
| 197 | + /// # Returns |
| 198 | + /// |
| 199 | + /// Returns a tuple containing: |
| 200 | + /// - A `MockGuard` the end-point mock is scoped to this guard |
| 201 | + /// - A `Future` that resolves to a `Raw<EncryptedToDeviceEvent>>` |
| 202 | + /// containing the captured encrypted to-device message. |
| 203 | + /// |
| 204 | + /// # Examples |
| 205 | + /// |
| 206 | + /// ```rust |
| 207 | + /// # use ruma::{ device_id, user_id, serde::Raw}; |
| 208 | + /// # use serde_json::json; |
| 209 | + /// |
| 210 | + /// # use matrix_sdk_test::async_test; |
| 211 | + /// # use matrix_sdk::test_utils::mocks::MatrixMockServer; |
| 212 | + /// # |
| 213 | + /// #[async_test] |
| 214 | + /// async fn test_mock_capture_put_to_device() { |
| 215 | + /// let server = MatrixMockServer::new().await; |
| 216 | + /// server.mock_crypto_endpoints_preset().await; |
| 217 | + /// |
| 218 | + /// let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await; |
| 219 | + /// let bob_user_id = bob.user_id().unwrap(); |
| 220 | + /// let bob_device_id = bob.device_id().unwrap(); |
| 221 | + /// |
| 222 | + /// // From the point of view of Alice, Bob now has a device. |
| 223 | + /// let alice_bob_device = alice |
| 224 | + /// .encryption() |
| 225 | + /// .get_device(bob_user_id, bob_device_id) |
| 226 | + /// .await |
| 227 | + /// .unwrap() |
| 228 | + /// .expect("alice sees bob's device"); |
| 229 | + /// |
| 230 | + /// let content_raw = Raw::new(&json!({ /*...*/ })).unwrap().cast(); |
| 231 | + /// |
| 232 | + /// // Set up the mock to capture encrypted to-device messages |
| 233 | + /// let (guard, captured) = |
| 234 | + /// server.mock_capture_put_to_device(alice.user_id().unwrap()).await; |
| 235 | + /// |
| 236 | + /// alice |
| 237 | + /// .encryption() |
| 238 | + /// .encrypt_and_send_raw_to_device( |
| 239 | + /// vec![&alice_bob_device], |
| 240 | + /// "call.keys", |
| 241 | + /// content_raw, |
| 242 | + /// ) |
| 243 | + /// .await |
| 244 | + /// .unwrap(); |
| 245 | + /// |
| 246 | + /// // this is the captured event as sent by alice! |
| 247 | + /// let sent_event = captured.await; |
| 248 | + /// drop(guard); |
| 249 | + /// } |
| 250 | + /// ``` |
| 251 | + pub async fn mock_capture_put_to_device( |
| 252 | + &self, |
| 253 | + sender_user_id: &UserId, |
| 254 | + ) -> (MockGuard, impl Future<Output = Raw<EncryptedToDeviceEvent>>) { |
| 255 | + let (tx, rx) = tokio::sync::oneshot::channel(); |
| 256 | + let tx = Arc::new(Mutex::new(Some(tx))); |
| 257 | + |
| 258 | + let sender = sender_user_id.to_owned(); |
| 259 | + let guard = Mock::given(method("PUT")) |
| 260 | + .and(path_regex(r"^/_matrix/client/.*/sendToDevice/m.room.encrypted/.*")) |
| 261 | + .respond_with(move |req: &Request| { |
| 262 | + #[derive(Debug, serde::Deserialize)] |
| 263 | + struct Parameters { |
| 264 | + messages: Messages, |
| 265 | + } |
| 266 | + |
| 267 | + let params: Parameters = req.body_json().unwrap(); |
| 268 | + |
| 269 | + let (_, device_to_content) = params.messages.first_key_value().unwrap(); |
| 270 | + let content = device_to_content.first_key_value().unwrap().1; |
| 271 | + |
| 272 | + let event = json!({ |
| 273 | + "origin_server_ts": MilliSecondsSinceUnixEpoch::now(), |
| 274 | + "sender": sender, |
| 275 | + "type": "m.room.encrypted", |
| 276 | + "content": content, |
| 277 | + }); |
| 278 | + let event: Raw<EncryptedToDeviceEvent> = serde_json::from_value(event).unwrap(); |
| 279 | + |
| 280 | + if let Ok(mut guard) = tx.lock() { |
| 281 | + if let Some(tx) = guard.take() { |
| 282 | + let _ = tx.send(event); |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + ResponseTemplate::new(200).set_body_json(&*test_json::EMPTY) |
| 287 | + }) |
| 288 | + // Should be called once |
| 289 | + .expect(1) |
| 290 | + .named("send_to_device") |
| 291 | + .mount_as_scoped(self.server()) |
| 292 | + .await; |
| 293 | + |
| 294 | + let future = |
| 295 | + async move { rx.await.expect("Failed to receive captured value - sender was dropped") }; |
| 296 | + |
| 297 | + (guard, future) |
| 298 | + } |
| 299 | + |
| 300 | + /// Captures a to-device message when it is sent to the mock server and then |
| 301 | + /// injects it into the recipient's sync response. |
| 302 | + /// |
| 303 | + /// This is a utility function that combines capturing an encrypted |
| 304 | + /// to-device message and delivering it to the recipient through a sync |
| 305 | + /// response. It's useful for testing end-to-end encryption scenarios |
| 306 | + /// where you need to verify message delivery and processing. |
| 307 | + /// |
| 308 | + /// # Arguments |
| 309 | + /// |
| 310 | + /// * `sender_user_id` - The user ID of the message sender |
| 311 | + /// * `recipient` - The client that will receive the message through sync |
| 312 | + /// |
| 313 | + /// # Returns |
| 314 | + /// |
| 315 | + /// Returns a `Future` that will resolve when the captured event has been |
| 316 | + /// fed back down the recipient sync. |
| 317 | + pub async fn mock_capture_put_to_device_then_sync_back<'a>( |
| 318 | + &'a self, |
| 319 | + sender_user_id: &UserId, |
| 320 | + recipient: &'a Client, |
| 321 | + ) -> impl Future<Output = Raw<EncryptedToDeviceEvent>> + 'a { |
| 322 | + let (guard, sent_event) = self.mock_capture_put_to_device(sender_user_id).await; |
| 323 | + |
| 324 | + async { |
| 325 | + let sent_event = sent_event.await; |
| 326 | + drop(guard); |
| 327 | + self.mock_sync() |
| 328 | + .ok_and_run(recipient, |sync_builder| { |
| 329 | + sync_builder.add_to_device_event(sent_event.deserialize_as().unwrap()); |
| 330 | + }) |
| 331 | + .await; |
| 332 | + |
| 333 | + sent_event |
| 334 | + } |
| 335 | + } |
181 | 336 | }
|
182 | 337 |
|
183 | 338 | /// Intercepts a `/keys/query` request and mock its results as returned by an
|
|
0 commit comments