task(tests): mock upload too

This commit is contained in:
Benjamin Bouvier
2024-11-06 17:27:43 +01:00
parent 57137cdd5b
commit f032d16d20
3 changed files with 169 additions and 188 deletions

View File

@@ -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<OwnedEventId>) -> 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<R: Respond + 'static>(self, func: R) -> MatrixMock<'a> {
MatrixMock { mock: self.mock.respond_with(func), server: self.server }
}
}

View File

@@ -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().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)
}))
.ok(expected_event_id)
.mock_once()
.mount()
.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)
.await;
// First request to /upload: return the thumbnail MXC.
mock.mock_upload().expect_mime_type("image/jpeg").ok(&thumbnail_mxc).mock_once().mount().await;
mock_sync(&server, &*test_json::SYNC, None).await;
mock_encryption_state(&server, false).await;
// Second request: return the media MXC.
mock.mock_upload().expect_mime_type("image/jpeg").ok(&media_mxc).mock_once().mount().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;
// Preconditions: nothing is found in the cache.
let media_request =
@@ -250,6 +198,8 @@ async fn test_room_attachment_send_info_thumbnail() {
source: MediaSource::Plain(thumbnail_mxc.clone()),
format: MediaFormat::Thumbnail(MediaThumbnailSettings::new(uint!(480), uint!(360))),
};
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();
@@ -277,7 +227,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.
@@ -311,38 +261,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(
@@ -355,5 +297,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);
}

View File

@@ -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,
};
@@ -38,32 +41,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<Mutex<()>>) -> Mock {
fn mock_jpeg_upload<'a>(
mock: &'a MatrixMockServer,
mxc: &MxcUri,
lock: Arc<Mutex<()>>,
) -> 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.
@@ -1751,16 +1750,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;
// ----------------------