From cfc43f4e9d27a136406cd23ce4ded785e5607495 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Wed, 6 Nov 2024 17:27:43 +0100 Subject: [PATCH] task(tests): mock upload too --- crates/matrix-sdk/src/test_utils/mocks.rs | 47 +++- .../tests/integration/room/attachment/mod.rs | 248 +++++++----------- .../tests/integration/send_queue.rs | 62 ++--- 3 files changed, 169 insertions(+), 188 deletions(-) diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs index b6af177f601..dba32f21c2b 100644 --- a/crates/matrix-sdk/src/test_utils/mocks.rs +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -24,10 +24,10 @@ use matrix_sdk_test::{ test_json, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, }; -use ruma::{OwnedEventId, OwnedRoomId, RoomId}; +use ruma::{MxcUri, OwnedEventId, OwnedRoomId, RoomId}; use serde_json::json; use wiremock::{ - matchers::{header, method, path, path_regex}, + matchers::{body_partial_json, header, method, path, path_regex}, Mock, MockBuilder, MockGuard, MockServer, Respond, ResponseTemplate, Times, }; @@ -193,6 +193,14 @@ impl MatrixMockServer { let mock = Mock::given(method("GET")).and(header("authorization", "Bearer 1234")); MockRoomEvent { mock, server: &self.server, room: None, match_event_id: false } } + + /// Create a prebuilt mock for uploading media. + pub fn mock_upload(&self) -> MockUpload<'_> { + let mock = Mock::given(method("POST")) + .and(path("/_matrix/media/r0/upload")) + .and(header("authorization", "Bearer 1234")); + MockUpload { mock, server: &self.server } + } } /// Parameter to [`MatrixMockServer::sync_room`]. @@ -303,6 +311,12 @@ pub struct MockRoomSend<'a> { } impl<'a> MockRoomSend<'a> { + /// Ensures that the body of the request is a superset of the provided + /// `body` parameter. + pub fn body_matches_partial_json(self, body: serde_json::Value) -> Self { + Self { mock: self.mock.and(body_partial_json(body)), ..self } + } + /// Returns a send endpoint that emulates success, i.e. the event has been /// sent with the given event id. pub fn ok(self, returned_event_id: impl Into) -> MatrixMock<'a> { @@ -469,3 +483,32 @@ impl<'a> MockRoomEvent<'a> { MatrixMock { server: self.server, mock } } } + +/// A prebuilt mock for uploading media. +pub struct MockUpload<'a> { + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockUpload<'a> { + /// Expect that the content type matches what's given here. + pub fn expect_mime_type(self, content_type: &str) -> Self { + Self { mock: self.mock.and(header("content-type", content_type)), ..self } + } + + /// Returns a redact endpoint that emulates success, i.e. the redaction + /// event has been sent with the given event id. + pub fn ok(self, mxc_id: &MxcUri) -> MatrixMock<'a> { + let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": mxc_id + }))); + MatrixMock { server: self.server, mock } + } + + /// Specify how to respond to a query (viz., like + /// [`MockBuilder::respond_with`] does), when other predefined responses + /// aren't sufficient. + pub fn respond_with(self, func: R) -> MatrixMock<'a> { + MatrixMock { mock: self.mock.respond_with(func), server: self.server } + } +} diff --git a/crates/matrix-sdk/tests/integration/room/attachment/mod.rs b/crates/matrix-sdk/tests/integration/room/attachment/mod.rs index 012a0832168..14d1b222669 100644 --- a/crates/matrix-sdk/tests/integration/room/attachment/mod.rs +++ b/crates/matrix-sdk/tests/integration/room/attachment/mod.rs @@ -1,62 +1,47 @@ -use std::{sync::Mutex, time::Duration}; +use std::time::Duration; use matrix_sdk::{ attachment::{ AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo, Thumbnail, }, - config::SyncSettings, media::{MediaFormat, MediaRequestParameters, MediaThumbnailSettings}, - test_utils::logged_in_client_with_server, + test_utils::mocks::MatrixMockServer, }; -use matrix_sdk_test::{async_test, mocks::mock_encryption_state, test_json, DEFAULT_TEST_ROOM_ID}; +use matrix_sdk_test::{async_test, DEFAULT_TEST_ROOM_ID}; use ruma::{ event_id, events::{room::MediaSource, Mentions}, - owned_mxc_uri, owned_user_id, uint, + mxc_uri, owned_mxc_uri, owned_user_id, uint, }; use serde_json::json; -use wiremock::{ - matchers::{body_partial_json, header, method, path, path_regex}, - Mock, ResponseTemplate, -}; - -use crate::mock_sync; #[async_test] async fn test_room_attachment_send() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .and(body_partial_json(json!({ + let expected_event_id = event_id!("$h29iv0s8:example.com"); + + mock.mock_room_send() + .body_matches_partial_json(json!({ "info": { "mimetype": "image/jpeg", } - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) - .mount(&server) + })) + .ok(expected_event_id) + .mock_once() + .mount() .await; - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }))) - .mount(&server) + mock.mock_upload() + .expect_mime_type("image/jpeg") + .ok(mxc_uri!("mxc://example.com/AQwafuaFswefuhsfAFAgsw")) + .mock_once() + .mount() .await; - mock_sync(&server, &*test_json::SYNC, None).await; - mock_encryption_state(&server, false).await; - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + let room = mock.sync_joined_room(&DEFAULT_TEST_ROOM_ID).await; + mock.mock_room_state_encryption().plain().mount().await; let response = room .send_attachment( @@ -68,45 +53,36 @@ async fn test_room_attachment_send() { .await .unwrap(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id); + assert_eq!(expected_event_id, response.event_id); } #[async_test] async fn test_room_attachment_send_info() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .and(body_partial_json(json!({ + let expected_event_id = event_id!("$h29iv0s8:example.com"); + mock.mock_room_send() + .body_matches_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, "w": 800, } - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) - .mount(&server) + })) + .ok(expected_event_id) + .mock_once() + .mount() .await; - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }))) - .mount(&server) + mock.mock_upload() + .expect_mime_type("image/jpeg") + .ok(mxc_uri!("mxc://example.com/AQwafuaFswefuhsfAFAgsw")) + .mock_once() + .mount() .await; - mock_sync(&server, &*test_json::SYNC, None).await; - mock_encryption_state(&server, false).await; - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + let room = mock.sync_joined_room(&DEFAULT_TEST_ROOM_ID).await; + mock.mock_room_state_encryption().plain().mount().await; let config = AttachmentConfig::new() .info(AttachmentInfo::Image(BaseImageInfo { @@ -122,46 +98,42 @@ async fn test_room_attachment_send_info() { .await .unwrap(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) + assert_eq!(expected_event_id, response.event_id) } #[async_test] async fn test_room_attachment_send_wrong_info() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .and(body_partial_json(json!({ + // Note: this mock is NOT called because the height and width are lost, because + // we're trying to send the attachment as an image, while we provide a + // `VideoInfo`. + // + // So long for static typing. + + mock.mock_room_send() + .body_matches_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, "w": 800, } - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) - .mount(&server) + })) + .ok(event_id!("$unused")) + .mount() .await; - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }))) - .mount(&server) + mock.mock_upload() + .expect_mime_type("image/jpeg") + .ok(mxc_uri!("mxc://example.com/yo")) + .mock_once() + .mount() .await; - mock_sync(&server, &*test_json::SYNC, None).await; - mock_encryption_state(&server, false).await; - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + let room = mock.sync_joined_room(&DEFAULT_TEST_ROOM_ID).await; + mock.mock_room_state_encryption().plain().mock_once().mount().await; + // Here, using `AttachmentInfo::Video`… let config = AttachmentConfig::new() .info(AttachmentInfo::Video(BaseVideoInfo { height: Some(uint!(600)), @@ -172,23 +144,26 @@ async fn test_room_attachment_send_wrong_info() { })) .caption(Some("image caption".to_owned())); + // But here, using `image/jpeg`. let response = room.send_attachment("image.jpg", &mime::IMAGE_JPEG, b"Hello world".to_vec(), config).await; + // In the real-world, this would lead to the size information getting lost, + // instead of an error during upload. …Is this test any useful? response.unwrap_err(); } #[async_test] async fn test_room_attachment_send_info_thumbnail() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; let media_mxc = owned_mxc_uri!("mxc://example.com/media"); let thumbnail_mxc = owned_mxc_uri!("mxc://example.com/thumbnail"); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .and(body_partial_json(json!({ + let expected_event_id = event_id!("$h29iv0s8:example.com"); + + mock.mock_room_send() + .body_matches_partial_json(json!({ "info": { "mimetype": "image/jpeg", "h": 600, @@ -201,47 +176,20 @@ async fn test_room_attachment_send_info_thumbnail() { }, "thumbnail_url": thumbnail_mxc, } - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) - .mount(&server) - .await; - - let counter = Mutex::new(0); - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with({ - // First request: return the thumbnail MXC; - // Second request: return the media MXC. - let media_mxc = media_mxc.clone(); - let thumbnail_mxc = thumbnail_mxc.clone(); - move |_: &wiremock::Request| { - let mut counter = counter.lock().unwrap(); - if *counter == 0 { - *counter += 1; - ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": &thumbnail_mxc - })) - } else { - ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": &media_mxc - })) - } - } - }) - .expect(2) - .mount(&server) + })) + .ok(expected_event_id) + .mock_once() + .mount() .await; - mock_sync(&server, &*test_json::SYNC, None).await; - mock_encryption_state(&server, false).await; + // First request to /upload: return the thumbnail MXC. + mock.mock_upload().expect_mime_type("image/jpeg").ok(&thumbnail_mxc).mock_once().mount().await; - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + // Second request: return the media MXC. + mock.mock_upload().expect_mime_type("image/jpeg").ok(&media_mxc).mock_once().mount().await; - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + let room = mock.sync_joined_room(&DEFAULT_TEST_ROOM_ID).await; + mock.mock_room_state_encryption().plain().mount().await; // Preconditions: nothing is found in the cache. let media_request = @@ -255,6 +203,8 @@ async fn test_room_attachment_send_info_thumbnail() { animated: false, }), }; + + let client = mock.client(); let _ = client.media().get_media_content(&media_request, true).await.unwrap_err(); let _ = client.media().get_media_content(&thumbnail_request, true).await.unwrap_err(); @@ -282,7 +232,7 @@ async fn test_room_attachment_send_info_thumbnail() { .unwrap(); // The event was sent. - assert_eq!(response.event_id, event_id!("$h29iv0s8:example.com")); + assert_eq!(response.event_id, expected_event_id); // The media is immediately cached in the cache store, so we don't need to set // up another mock endpoint for getting the media. @@ -321,38 +271,30 @@ async fn test_room_attachment_send_info_thumbnail() { #[async_test] async fn test_room_attachment_send_mentions() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .and(body_partial_json(json!({ + let expected_event_id = event_id!("$h29iv0s8:example.com"); + + mock.mock_room_send() + .body_matches_partial_json(json!({ "m.mentions": { "user_ids": ["@user:localhost"], } - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(&*test_json::EVENT_ID)) - .mount(&server) + })) + .ok(expected_event_id) + .mock_once() + .mount() .await; - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": "mxc://example.com/AQwafuaFswefuhsfAFAgsw" - }))) - .mount(&server) + mock.mock_upload() + .expect_mime_type("image/jpeg") + .ok(mxc_uri!("mxc://example.com/AQwafuaFswefuhsfAFAgsw")) + .mock_once() + .mount() .await; - mock_sync(&server, &*test_json::SYNC, None).await; - mock_encryption_state(&server, false).await; - - let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - - let _response = client.sync_once(sync_settings).await.unwrap(); - - let room = client.get_room(&DEFAULT_TEST_ROOM_ID).unwrap(); + let room = mock.sync_joined_room(&DEFAULT_TEST_ROOM_ID).await; + mock.mock_room_state_encryption().plain().mount().await; let response = room .send_attachment( @@ -365,5 +307,5 @@ async fn test_room_attachment_send_mentions() { .await .unwrap(); - assert_eq!(event_id!("$h29iv0s8:example.com"), response.event_id) + assert_eq!(expected_event_id, response.event_id); } diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index f9d4c0361cf..8ba89ffbab6 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -10,7 +10,10 @@ use matrix_sdk::{ RoomSendQueueUpdate, }, test_utils::{ - events::EventFactory, logged_in_client, mocks::MatrixMockServer, set_client_session, + events::EventFactory, + logged_in_client, + mocks::{MatrixMock, MatrixMockServer}, + set_client_session, }, Client, MemoryStore, }; @@ -39,32 +42,28 @@ use tokio::{ sync::Mutex, time::{sleep, timeout}, }; -use wiremock::{ - matchers::{header, method, path}, - Mock, Request, ResponseTemplate, -}; +use wiremock::{Request, ResponseTemplate}; -// TODO put into the MatrixMockServer -fn mock_jpeg_upload(mxc: &MxcUri, lock: Arc>) -> Mock { +fn mock_jpeg_upload<'a>( + mock: &'a MatrixMockServer, + mxc: &MxcUri, + lock: Arc>, +) -> MatrixMock<'a> { let mxc = mxc.to_owned(); - Mock::given(method("POST")) - .and(path("/_matrix/media/r0/upload")) - .and(header("authorization", "Bearer 1234")) - .and(header("content-type", "image/jpeg")) - .respond_with(move |_req: &Request| { - // Wait for the signal from the main task that we can process this query. - let mock_lock = lock.clone(); - std::thread::spawn(move || { - tokio::runtime::Runtime::new().unwrap().block_on(async { - drop(mock_lock.lock().await); - }); - }) - .join() - .unwrap(); - ResponseTemplate::new(200).set_body_json(json!({ - "content_uri": mxc - })) + mock.mock_upload().expect_mime_type("image/jpeg").respond_with(move |_req: &Request| { + // Wait for the signal from the main task that we can process this query. + let mock_lock = lock.clone(); + std::thread::spawn(move || { + tokio::runtime::Runtime::new().unwrap().block_on(async { + drop(mock_lock.lock().await); + }); }) + .join() + .unwrap(); + ResponseTemplate::new(200).set_body_json(json!({ + "content_uri": mxc + })) + }) } // A macro to assert on a stream of `RoomSendQueueUpdate`s. @@ -1752,16 +1751,13 @@ async fn test_media_uploads() { let allow_upload_lock = Arc::new(Mutex::new(())); let block_upload = allow_upload_lock.lock().await; - let server = mock.server(); - mock_jpeg_upload(mxc_uri!("mxc://sdk.rs/thumbnail"), allow_upload_lock.clone()) - .up_to_n_times(1) - .expect(1) - .mount(&server) + mock_jpeg_upload(&mock, mxc_uri!("mxc://sdk.rs/thumbnail"), allow_upload_lock.clone()) + .mock_once() + .mount() .await; - mock_jpeg_upload(mxc_uri!("mxc://sdk.rs/media"), allow_upload_lock.clone()) - .up_to_n_times(1) - .expect(1) - .mount(&server) + mock_jpeg_upload(&mock, mxc_uri!("mxc://sdk.rs/media"), allow_upload_lock.clone()) + .mock_once() + .mount() .await; // ----------------------