diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index c63a0e42968..65dcb153196 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -343,7 +343,7 @@ impl SyncTimelineEvent { /// Get the event id of this `SyncTimelineEvent` if the event has any valid /// id. pub fn event_id(&self) -> Option { - self.kind.raw().get_field::("event_id").ok().flatten() + self.kind.event_id() } /// Returns a reference to the (potentially decrypted) Matrix event inside @@ -529,6 +529,12 @@ impl TimelineEventKind { } } + /// Get the event id of this `TimelineEventKind` if the event has any valid + /// id. + pub fn event_id(&self) -> Option { + self.raw().get_field::("event_id").ok().flatten() + } + /// If the event was a decrypted event that was successfully decrypted, get /// its encryption info. Otherwise, `None`. pub fn encryption_info(&self) -> Option<&EncryptionInfo> { diff --git a/crates/matrix-sdk/src/test_utils.rs b/crates/matrix-sdk/src/test_utils.rs index f016503e85a..7b1e498254d 100644 --- a/crates/matrix-sdk/src/test_utils.rs +++ b/crates/matrix-sdk/src/test_utils.rs @@ -1,5 +1,6 @@ //! Testing utilities - DO NOT USE IN PRODUCTION. +#![allow(missing_docs)] #![allow(dead_code)] use assert_matches2::assert_let; @@ -14,6 +15,9 @@ use url::Url; pub mod events; +#[cfg(not(target_arch = "wasm32"))] +pub mod mocks; + use crate::{ config::RequestConfig, matrix_auth::{MatrixSession, MatrixSessionTokens}, diff --git a/crates/matrix-sdk/src/test_utils/events.rs b/crates/matrix-sdk/src/test_utils/events.rs index 3ecaebb5b47..03ddd9dd030 100644 --- a/crates/matrix-sdk/src/test_utils/events.rs +++ b/crates/matrix-sdk/src/test_utils/events.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![allow(missing_docs)] - use std::sync::atomic::{AtomicU64, Ordering::SeqCst}; use as_variant::as_variant; diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs new file mode 100644 index 00000000000..32a1b932c84 --- /dev/null +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -0,0 +1,389 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(missing_debug_implementations)] + +use std::sync::{Arc, Mutex}; + +use matrix_sdk_base::deserialized_responses::TimelineEvent; +use matrix_sdk_test::{ + test_json, InvitedRoomBuilder, JoinedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder, + SyncResponseBuilder, +}; +use ruma::{OwnedEventId, OwnedRoomId, RoomId}; +use serde_json::json; +use wiremock::{ + matchers::{header, method, path, path_regex}, + Mock, MockBuilder, MockGuard, MockServer, Respond, ResponseTemplate, Times, +}; + +use super::logged_in_client; +use crate::{Client, Room}; + +pub struct MatrixMockServer { + server: MockServer, + client: Client, + + /// Make the sync response builder stateful, to keep in memory the batch + /// token and avoid the client ignoring subsequent responses after the first + /// one. + sync_response_builder: Arc>, +} + +impl MatrixMockServer { + /// Create a new mocked server specialized for Matrix usage. + pub async fn new() -> Self { + let server = MockServer::start().await; + let client = logged_in_client(Some(server.uri().to_string())).await; + Self { client, server, sync_response_builder: Default::default() } + } + + /// Creates a new [`MatrixMockServer`] when both parts have been already + /// created. + pub fn from_pair(server: MockServer, client: Client) -> Self { + Self { client, server, sync_response_builder: Default::default() } + } + + /// Return the underlying client. + pub fn client(&self) -> Client { + self.client.clone() + } + + /// Return the underlying server. + pub fn server(&self) -> &MockServer { + &self.server + } + + /// Overrides the sync/ endpoint with knowledge that the given + /// invited/joined/knocked/left room exists, runs a sync and returns the + /// given room. + pub async fn sync_room(&self, room_id: &RoomId, room_data: impl Into) -> Room { + let any_room = room_data.into(); + + self.mock_sync() + .ok_and_run(move |builder| match any_room { + AnyRoomBuilder::Invited(invited) => { + builder.add_invited_room(invited); + } + AnyRoomBuilder::Joined(joined) => { + builder.add_joined_room(joined); + } + AnyRoomBuilder::Left(left) => { + builder.add_left_room(left); + } + AnyRoomBuilder::Knocked(knocked) => { + builder.add_knocked_room(knocked); + } + }) + .await; + + self.client.get_room(room_id).expect("look at me, the room is known now") + } + + /// Overrides the sync/ endpoint with knowledge that the given room exists + /// in the joined state, runs a sync and returns the given room. + pub async fn sync_joined_room(&self, room_id: &RoomId) -> Room { + self.sync_room(room_id, JoinedRoomBuilder::new(room_id)).await + } + + pub async fn verify_and_reset(&self) { + self.server.verify().await; + self.server.reset().await; + } +} + +// Specific mount endpoints. +impl MatrixMockServer { + /// Mocks a sync endpoint. + pub fn mock_sync(&self) -> MockSync<'_> { + let mock = Mock::given(method("GET")) + .and(path("/_matrix/client/r0/sync")) + .and(header("authorization", "Bearer 1234")); + MockSync { + mock, + client: self.client.clone(), + server: &self.server, + sync_response_builder: self.sync_response_builder.clone(), + } + } + + /// Creates a prebuilt mock for sending an event in a room. + /// + /// Note: works with *any* room. + pub fn mock_room_send(&self) -> MockRoomSend<'_> { + let mock = Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) + .and(header("authorization", "Bearer 1234")); + MockRoomSend { mock, server: &self.server } + } + + /// Creates a prebuilt mock for asking whether *a* room is encrypted or not. + /// + /// Note: Applies to all rooms. + pub fn mock_room_state_encryption(&self) -> MockEncryptionState<'_> { + let mock = Mock::given(method("GET")) + .and(header("authorization", "Bearer 1234")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?")); + MockEncryptionState { mock, server: &self.server } + } + + pub fn mock_set_room_state_encryption(&self) -> MockSetEncryptionState<'_> { + let mock = Mock::given(method("PUT")) + .and(header("authorization", "Bearer 1234")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?")); + MockSetEncryptionState { mock, server: &self.server } + } + + /// Creates a prebuilt mock for the room redact endpoint. + pub fn mock_room_redact(&self) -> MockRoomRedact<'_> { + let mock = Mock::given(method("PUT")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?")) + .and(header("authorization", "Bearer 1234")); + MockRoomRedact { mock, server: &self.server } + } + + /// Creates a prebuilt mock for retrieving an event with /room/.../event. + pub fn mock_room_event(&self) -> MockRoomEvent<'_> { + let mock = Mock::given(method("GET")).and(header("authorization", "Bearer 1234")); + MockRoomEvent { mock, server: &self.server, room: None, match_event_id: false } + } +} + +/// Parameter to [`MatrixMockServer::sync_room`]. +pub enum AnyRoomBuilder { + Invited(InvitedRoomBuilder), + Joined(JoinedRoomBuilder), + Left(LeftRoomBuilder), + Knocked(KnockedRoomBuilder), +} + +impl From for AnyRoomBuilder { + fn from(val: InvitedRoomBuilder) -> AnyRoomBuilder { + AnyRoomBuilder::Invited(val) + } +} + +impl From for AnyRoomBuilder { + fn from(val: JoinedRoomBuilder) -> AnyRoomBuilder { + AnyRoomBuilder::Joined(val) + } +} + +impl From for AnyRoomBuilder { + fn from(val: LeftRoomBuilder) -> AnyRoomBuilder { + AnyRoomBuilder::Left(val) + } +} + +impl From for AnyRoomBuilder { + fn from(val: KnockedRoomBuilder) -> AnyRoomBuilder { + AnyRoomBuilder::Knocked(val) + } +} + +/// A wiremock Mock wrapping also the server, to not have to pass it around when +/// mounting. +pub struct MatrixMock<'a> { + mock: Mock, + server: &'a MockServer, +} + +impl<'a> MatrixMock<'a> { + pub fn expect>(self, num_calls: T) -> Self { + Self { mock: self.mock.expect(num_calls), ..self } + } + pub fn named(self, name: impl Into) -> Self { + Self { mock: self.mock.named(name), ..self } + } + /// Respond to a response of this endpoint exactly once. + /// + /// After it's been called, subsequent responses will hit the next handler + /// or a 404. + /// + /// Also verifies that it's been called once. + pub fn mock_once(self) -> Self { + Self { mock: self.mock.up_to_n_times(1).expect(1), ..self } + } + pub async fn mount(self) { + self.mock.mount(self.server).await; + } + pub async fn mount_as_scoped(self) -> MockGuard { + self.mock.mount_as_scoped(self.server).await + } +} + +pub struct MockRoomSend<'a> { + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockRoomSend<'a> { + /// 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> { + let returned_event_id = returned_event_id.into(); + MatrixMock { + mock: self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "event_id": returned_event_id + }))), + server: self.server, + } + } + + /// Returns a send endpoint that emulates a transient failure, i.e responds + /// with error 500. + pub fn error500(self) -> MatrixMock<'a> { + MatrixMock { mock: self.mock.respond_with(ResponseTemplate::new(500)), server: self.server } + } + + /// Returns a send endpoint that emulates a permanent failure (event is too + /// large). + pub fn error_too_large(self) -> MatrixMock<'a> { + MatrixMock { + mock: self.mock.respond_with(ResponseTemplate::new(413).set_body_json(json!({ + // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response + "errcode": "M_TOO_LARGE", + }))), + server: self.server, + } + } + + pub fn respond_with(self, func: R) -> MatrixMock<'a> { + MatrixMock { mock: self.mock.respond_with(func), server: self.server } + } +} + +pub struct MockSync<'a> { + mock: MockBuilder, + server: &'a MockServer, + sync_response_builder: Arc>, + client: Client, +} + +impl<'a> MockSync<'a> { + /// Temporarily mocks the sync with the given endpoint and runs a client + /// sync with it. + /// + /// After calling this function, the sync endpoint isn't mocked anymore. + pub async fn ok_and_run(self, func: F) { + let json_response = { + let mut builder = self.sync_response_builder.lock().unwrap(); + func(&mut builder); + builder.build_json_sync_response() + }; + + let _scope = self + .mock + .respond_with(ResponseTemplate::new(200).set_body_json(json_response)) + .mount_as_scoped(self.server) + .await; + + let _response = self.client.sync_once(Default::default()).await.unwrap(); + } +} + +pub struct MockEncryptionState<'a> { + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockEncryptionState<'a> { + /// Marks the room as encrypted. + pub fn encrypted(self) -> MatrixMock<'a> { + let mock = self.mock.respond_with( + ResponseTemplate::new(200).set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT), + ); + MatrixMock { mock, server: self.server } + } + + /// Marks the room as not encrypted. + pub fn plain(self) -> MatrixMock<'a> { + let mock = self + .mock + .respond_with(ResponseTemplate::new(404).set_body_json(&*test_json::NOT_FOUND)); + MatrixMock { mock, server: self.server } + } +} + +pub struct MockSetEncryptionState<'a> { + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockSetEncryptionState<'a> { + /// Returns a mock for a successful setting of the encryption state event. + pub fn ok(self, returned_event_id: impl Into) -> MatrixMock<'a> { + let event_id = returned_event_id.into(); + let mock = self.mock.respond_with( + ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })), + ); + MatrixMock { server: self.server, mock } + } +} + +pub struct MockRoomRedact<'a> { + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockRoomRedact<'a> { + /// Returns a redact endpoint that emulates success, i.e. the redaction + /// event has been sent with the given event id. + pub fn ok(self, returned_event_id: impl Into) -> MatrixMock<'a> { + let event_id = returned_event_id.into(); + let mock = self.mock.respond_with( + ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })), + ); + MatrixMock { server: self.server, mock } + } +} + +pub struct MockRoomEvent<'a> { + room: Option, + match_event_id: bool, + server: &'a MockServer, + mock: MockBuilder, +} + +impl<'a> MockRoomEvent<'a> { + /// Limits the scope of this mock to a specific room. + pub fn room(self, room: impl Into) -> Self { + Self { room: Some(room.into()), ..self } + } + + /// Whether the mock checks for the event id from the event. + pub fn match_event_id(self) -> Self { + Self { match_event_id: true, ..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, event: TimelineEvent) -> MatrixMock<'a> { + let event_path = if self.match_event_id { + let event_id = event.kind.event_id().expect("an event id is required"); + event_id.to_string() + } else { + // Event is at the end, so no need to add anything. + "".to_owned() + }; + + let room_path = self.room.map_or_else(|| ".*".to_owned(), |room| room.to_string()); + + let mock = self + .mock + .and(path_regex(format!("^/_matrix/client/r0/rooms/{room_path}/event/{event_path}"))) + .respond_with(ResponseTemplate::new(200).set_body_json(event.into_raw().json())); + MatrixMock { server: self.server, mock } + } +} diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs index 13cebccde96..10205a5604f 100644 --- a/crates/matrix-sdk/tests/integration/main.rs +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -1,9 +1,8 @@ // The http mocking library is not supported for wasm32 #![cfg(not(target_arch = "wasm32"))] -use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server, Client, Room}; -use matrix_sdk_test::{test_json, SyncResponseBuilder}; -use ruma::RoomId; +use matrix_sdk::{config::SyncSettings, test_utils::logged_in_client_with_server, Client}; +use matrix_sdk_test::test_json; use serde::Serialize; use wiremock::{ matchers::{header, method, path, query_param, query_param_is_missing}, @@ -79,28 +78,3 @@ async fn mock_sync_scoped( .mount_as_scoped(server) .await } - -/// Does a sync for a given room, and returns its `Room` object. -/// -/// Note this sync is token-less. -async fn mock_sync_with_new_room( - func: F, - client: &Client, - server: &MockServer, - room_id: &RoomId, -) -> Room { - let mut sync_response_builder = SyncResponseBuilder::default(); - func(&mut sync_response_builder); - let json_response = sync_response_builder.build_json_sync_response(); - - let _scope = Mock::given(method("GET")) - .and(path("/_matrix/client/r0/sync")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(json_response)) - .mount_as_scoped(server) - .await; - - let _response = client.sync_once(Default::default()).await.unwrap(); - - client.get_room(room_id).expect("we should find the room we just sync'd from") -} diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index e8f294bb0d0..70e4d31200d 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -7,7 +7,7 @@ use futures_util::future::join_all; use matrix_sdk::{ config::SyncSettings, room::{edit::EditedContent, Receipts, ReportedContentScore, RoomMemberRole}, - test_utils::events::EventFactory, + test_utils::{events::EventFactory, mocks::MatrixMockServer}, }; use matrix_sdk_base::RoomState; use matrix_sdk_test::{ @@ -33,7 +33,7 @@ use wiremock::{ Mock, ResponseTemplate, }; -use crate::{logged_in_client_with_server, mock_sync, mock_sync_with_new_room, synced_client}; +use crate::{logged_in_client_with_server, mock_sync, synced_client}; #[async_test] async fn test_invite_user_by_id() { let (client, server) = logged_in_client_with_server().await; @@ -720,78 +720,43 @@ async fn test_make_reply_event_doesnt_require_event_cache() { // Even if we don't have enabled the event cache, we'll resort to using the // /event query to get details on an event. - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; + let user_id = mock.client().user_id().unwrap().to_owned(); - let event_id = event_id!("$1"); - let resp_event_id = event_id!("$resp"); let room_id = room_id!("!galette:saucisse.bzh"); + let room = mock.sync_joined_room(room_id).await; + let event_id = event_id!("$1"); let f = EventFactory::new(); - - let raw_original_event = f - .text_msg("hi") - .event_id(event_id) - .sender(client.user_id().unwrap()) - .room(room_id) - .into_raw_timeline(); - - mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; - - Mock::given(method("GET")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(raw_original_event.json())) + mock.mock_room_event() + .ok(f.text_msg("hi").event_id(event_id).sender(&user_id).room(room_id).into_timeline()) .expect(1) .named("/event") - .mount(&server) + .mount() .await; let new_content = RoomMessageEventContentWithoutRelation::text_plain("uh i mean bonjour"); - let room = client.get_room(room_id).unwrap(); - // make_edit_event works, even if the event cache hasn't been enabled. - room.make_edit_event(resp_event_id, EditedContent::RoomMessage(new_content)).await.unwrap(); + room.make_edit_event(event_id, EditedContent::RoomMessage(new_content)).await.unwrap(); } #[async_test] async fn test_enable_encryption_doesnt_stay_unencrypted() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; - mock_encryption_state(&server, false).await; - - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ "event_id": "$1"}))) - .mount(&server) - .await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_set_room_state_encryption().ok(event_id!("$1")).mount().await; let room_id = room_id!("!a:b.c"); - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; assert!(!room.is_encrypted().await.unwrap()); room.enable_encryption().await.expect("enabling encryption should work"); - server.reset().await; - mock_encryption_state(&server, true).await; + mock.verify_and_reset().await; + mock.mock_room_state_encryption().encrypted().mount().await; assert!(room.is_encrypted().await.unwrap()); } diff --git a/crates/matrix-sdk/tests/integration/send_queue.rs b/crates/matrix-sdk/tests/integration/send_queue.rs index 55455dd0ff9..d359bddd96a 100644 --- a/crates/matrix-sdk/tests/integration/send_queue.rs +++ b/crates/matrix-sdk/tests/integration/send_queue.rs @@ -1,26 +1,15 @@ -use std::{ - ops::Not as _, - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, Mutex as StdMutex, - }, - time::Duration, -}; +use std::{ops::Not as _, sync::Arc, time::Duration}; use assert_matches2::{assert_let, assert_matches}; use matrix_sdk::{ config::{RequestConfig, StoreConfig}, send_queue::{LocalEcho, LocalEchoContent, RoomSendQueueError, RoomSendQueueUpdate}, test_utils::{ - events::EventFactory, logged_in_client, logged_in_client_with_server, set_client_session, + events::EventFactory, logged_in_client, mocks::MatrixMockServer, set_client_session, }, Client, MemoryStore, }; -use matrix_sdk_test::{ - async_test, - mocks::{mock_encryption_state, mock_redaction}, - InvitedRoomBuilder, JoinedRoomBuilder, LeftRoomBuilder, -}; +use matrix_sdk_test::{async_test, InvitedRoomBuilder, KnockedRoomBuilder, LeftRoomBuilder}; use ruma::{ api::MatrixVersion, event_id, @@ -34,37 +23,14 @@ use ruma::{ }, room_id, serde::Raw, - EventId, OwnedEventId, + OwnedEventId, }; use serde_json::json; use tokio::{ sync::Mutex, time::{sleep, timeout}, }; -use wiremock::{ - matchers::{header, method, path_regex}, - Mock, Request, ResponseTemplate, -}; - -use crate::mock_sync_with_new_room; - -fn mock_send_event(returned_event_id: &EventId) -> Mock { - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "event_id": returned_event_id, - }))) -} - -/// Return a mock that will fail all requests to /rooms/ROOM_ID/send with a -/// transient 500 error. -fn mock_send_transient_failure() -> Mock { - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(500)) -} +use wiremock::{Request, ResponseTemplate}; // A macro to assert on a stream of `RoomSendQueueUpdate`s. macro_rules! assert_update { @@ -177,20 +143,11 @@ macro_rules! assert_update { #[async_test] async fn test_cant_send_invited_room() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // When I'm invited to a room, let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_invited_room(InvitedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_room(room_id, InvitedRoomBuilder::new(room_id)).await; // I can't send message to it with the send queue. assert_matches!( @@ -201,20 +158,28 @@ async fn test_cant_send_invited_room() { #[async_test] async fn test_cant_send_left_room() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // When I've left a room, let room_id = room_id!("!a:b.c"); + let room = mock.sync_room(room_id, LeftRoomBuilder::new(room_id)).await; - let room = mock_sync_with_new_room( - |builder| { - builder.add_left_room(LeftRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + // I can't send message to it with the send queue. + assert_matches!( + room.send_queue() + .send(RoomMessageEventContent::text_plain("Farewell, World!").into()) + .await, + Err(RoomSendQueueError::RoomNotJoined) + ); +} + +#[async_test] +async fn test_cant_send_knocked_room() { + let mock = MatrixMockServer::new().await; + + // When I've knocked into a room, + let room_id = room_id!("!a:b.c"); + let room = mock.sync_room(room_id, KnockedRoomBuilder::new(room_id)).await; // I can't send message to it with the send queue. assert_matches!( @@ -227,26 +192,17 @@ async fn test_cant_send_left_room() { #[async_test] async fn test_nothing_sent_when_disabled() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; // When I disable the send queue, let event_id = event_id!("$1"); - mock_send_event(event_id).expect(0).mount(&server).await; + mock.mock_room_send().ok(event_id).expect(0).mount().await; - client.send_queue().set_enabled(false).await; + mock.client().send_queue().set_enabled(false).await; // A message is queued, but never sent. room.send_queue() @@ -255,11 +211,10 @@ async fn test_nothing_sent_when_disabled() { .unwrap(); // But I can still send it with room.send(). - server.verify().await; - server.reset().await; + mock.verify_and_reset().await; - mock_encryption_state(&server, false).await; - mock_send_event(event_id).expect(1).mount(&server).await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_room_send().ok(event_id).expect(1).mount().await; let response = room.send(RoomMessageEventContent::text_plain("Hello, World!")).await.unwrap(); assert_eq!(response.event_id, event_id); @@ -267,20 +222,11 @@ async fn test_nothing_sent_when_disabled() { #[async_test] async fn test_smoke() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -296,11 +242,9 @@ async fn test_smoke() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -317,7 +261,7 @@ async fn test_smoke() { })) }) .expect(1) - .mount(&server) + .mount() .await; room.send_queue().send(RoomMessageEventContent::text_plain("1").into()).await.unwrap(); @@ -342,20 +286,11 @@ async fn test_smoke() { #[async_test] async fn test_smoke_raw() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -366,8 +301,8 @@ async fn test_smoke_raw() { // When the queue is enabled and I send message in some order, it does send it. let event_id = event_id!("$1"); - mock_encryption_state(&server, false).await; - mock_send_event(event_id!("$1")).mount(&server).await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_room_send().ok(event_id).mount().await; let json_content = r#"{"baguette": 42}"#.to_owned(); let event = Raw::from_json_string(json_content.clone()).unwrap(); @@ -395,8 +330,9 @@ async fn test_smoke_raw() { #[async_test] async fn test_error_then_locally_reenabling() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; + let client = mock.client(); let mut errors = client.send_queue().subscribe_errors(); // Starting with a globally enabled queue. @@ -405,16 +341,7 @@ async fn test_error_then_locally_reenabling() { // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -427,11 +354,10 @@ async fn test_error_then_locally_reenabling() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + let scoped_send = mock + .mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -446,7 +372,7 @@ async fn test_error_then_locally_reenabling() { ResponseTemplate::new(500) }) .expect(3) - .mount(&server) + .mount_as_scoped() .await; q.send(RoomMessageEventContent::text_plain("1").into()).await.unwrap(); @@ -487,8 +413,8 @@ async fn test_error_then_locally_reenabling() { // But the room send queue is disabled. assert!(!room.send_queue().is_enabled()); - server.reset().await; - mock_send_event(event_id!("$42")).expect(1).mount(&server).await; + drop(scoped_send); + mock.mock_room_send().ok(event_id!("$42")).expect(1).mount().await; // Re-enabling the *room* queue will re-send the same message in that room. room.send_queue().set_enabled(true); @@ -506,8 +432,9 @@ async fn test_error_then_locally_reenabling() { #[async_test] async fn test_error_then_globally_reenabling() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; + let client = mock.client(); let mut errors = client.send_queue().subscribe_errors(); // Starting with a globally enabled queue. @@ -516,16 +443,7 @@ async fn test_error_then_globally_reenabling() { // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -533,9 +451,9 @@ async fn test_error_then_globally_reenabling() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - server.reset().await; - mock_encryption_state(&server, false).await; - mock_send_transient_failure().expect(3).mount(&server).await; + mock.verify_and_reset().await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_room_send().error500().expect(3).mount().await; q.send(RoomMessageEventContent::text_plain("1").into()).await.unwrap(); @@ -561,9 +479,9 @@ async fn test_error_then_globally_reenabling() { assert!(watch.is_empty()); - server.reset().await; - mock_encryption_state(&server, false).await; - mock_send_event(event_id!("$42")).expect(1).mount(&server).await; + mock.verify_and_reset().await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_room_send().ok(event_id!("$42")).expect(1).mount().await; // Re-enabling the global queue will cause the event to be sent. client.send_queue().set_enabled(true).await; @@ -579,21 +497,13 @@ async fn test_error_then_globally_reenabling() { #[async_test] async fn test_reenabling_queue() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); + let room = mock.sync_joined_room(room_id).await; - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; - + let client = mock.client(); let errors = client.send_queue().subscribe_errors(); assert!(errors.is_empty()); @@ -633,25 +543,11 @@ async fn test_reenabling_queue() { assert!(watch.is_empty()); - mock_encryption_state(&server, false).await; - - let num_request = std::sync::Mutex::new(1); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(move |_req: &Request| { - let mut num_request = num_request.lock().unwrap(); - - let event_id = format!("${}", *num_request); - *num_request += 1; + mock.mock_room_state_encryption().plain().mount().await; - ResponseTemplate::new(200).set_body_json(json!({ - "event_id": event_id, - })) - }) - .expect(3) - .mount(&server) - .await; + mock.mock_room_send().ok(event_id!("$1")).mock_once().mount().await; + mock.mock_room_send().ok(event_id!("$2")).mock_once().mount().await; + mock.mock_room_send().ok(event_id!("$3")).mock_once().mount().await; // But when reenabling the queue globally, client.send_queue().set_enabled(true).await; @@ -672,23 +568,16 @@ async fn test_reenabling_queue() { #[async_test] async fn test_disjoint_enabled_status() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id1 = room_id!("!a:b.c"); let room_id2 = room_id!("!b:b.c"); - let room1 = mock_sync_with_new_room( - |builder| { - builder - .add_joined_room(JoinedRoomBuilder::new(room_id1)) - .add_joined_room(JoinedRoomBuilder::new(room_id2)); - }, - &client, - &server, - room_id1, - ) - .await; - let room2 = client.get_room(room_id2).unwrap(); + + let room1 = mock.sync_joined_room(room_id1).await; + let room2 = mock.sync_joined_room(room_id2).await; + + let client = mock.client(); // When I start with a disabled send queue, client.send_queue().set_enabled(false).await; @@ -717,20 +606,11 @@ async fn test_disjoint_enabled_status() { #[async_test] async fn test_cancellation() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -744,12 +624,10 @@ async fn test_cancellation() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; let num_request = std::sync::Mutex::new(1); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -771,11 +649,11 @@ async fn test_cancellation() { })) }) .expect(2) - .mount(&server) + .mount() .await; // The redact of txn1 will happen because we asked for it previously. - mock_redaction(event_id!("$1")).expect(1).mount(&server).await; + mock.mock_room_redact().ok(event_id!("$1")).mount().await; let handle1 = q.send(RoomMessageEventContent::text_plain("msg1").into()).await.unwrap(); let handle2 = q.send(RoomMessageEventContent::text_plain("msg2").into()).await.unwrap(); @@ -844,20 +722,11 @@ async fn test_edit() { // to edit a local echo, since if the cancellation test passes, all ways // would work here too similarly. - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -871,12 +740,10 @@ async fn test_edit() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; let num_request = std::sync::Mutex::new(1); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -898,27 +765,22 @@ async fn test_edit() { })) }) .expect(3) - .mount(&server) + .mount() .await; // The /event endpoint is used to retrieve the original event, during creation // of the edit event. - Mock::given(method("GET")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/")) - .and(header("authorization", "Bearer 1234")) - .respond_with( - ResponseTemplate::new(200).set_body_json( - EventFactory::new() - .text_msg("msg1") - .sender(client.user_id().unwrap()) - .room(room_id) - .into_raw_timeline() - .json(), - ), - ) + let client = mock.client(); + mock.mock_room_event() + .room(room_id) + .ok(EventFactory::new() + .text_msg("msg1") + .sender(client.user_id().unwrap()) + .room(room_id) + .into_timeline()) .expect(1) - .named("get_event") - .mount(&server) + .named("room_event") + .mount() .await; let handle1 = q.send(RoomMessageEventContent::text_plain("msg1").into()).await.unwrap(); @@ -964,20 +826,11 @@ async fn test_edit() { #[async_test] async fn test_edit_with_poll_start() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -991,12 +844,10 @@ async fn test_edit_with_poll_start() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; let num_request = std::sync::Mutex::new(1); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -1019,27 +870,21 @@ async fn test_edit_with_poll_start() { }) .named("send_event") .expect(2) - .mount(&server) + .mount() .await; // The /event endpoint is used to retrieve the original event, during creation // of the edit event. - Mock::given(method("GET")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/")) - .and(header("authorization", "Bearer 1234")) - .respond_with( - ResponseTemplate::new(200).set_body_json( - EventFactory::new() - .poll_start("poll_start", "question", vec!["Answer A"]) - .sender(client.user_id().unwrap()) - .room(room_id) - .into_raw_timeline() - .json(), - ), - ) + let client = mock.client(); + mock.mock_room_event() + .ok(EventFactory::new() + .poll_start("poll_start", "question", vec!["Answer A"]) + .sender(client.user_id().unwrap()) + .room(room_id) + .into_timeline()) .expect(1) .named("get_event") - .mount(&server) + .mount() .await; let poll_answers: UnstablePollAnswers = @@ -1109,20 +954,11 @@ async fn test_edit_with_poll_start() { #[async_test] async fn test_edit_while_being_sent_and_fails() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -1136,11 +972,9 @@ async fn test_edit_while_being_sent_and_fails() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -1155,7 +989,7 @@ async fn test_edit_while_being_sent_and_fails() { ResponseTemplate::new(500) }) .expect(3) // reattempts, because of short_retry() - .mount(&server) + .mount() .await; let handle = q.send(RoomMessageEventContent::text_plain("yo").into()).await.unwrap(); @@ -1200,20 +1034,11 @@ async fn test_edit_while_being_sent_and_fails() { #[async_test] async fn test_edit_wakes_the_sending_task() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -1222,18 +1047,9 @@ async fn test_edit_wakes_the_sending_task() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - let send_mock_scope = Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(413).set_body_json(json!({ - // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response - "errcode": "M_TOO_LARGE", - }))) - .expect(1) - .mount_as_scoped(&server) - .await; + let send_mock_scope = mock.mock_room_send().error_too_large().expect(1).mount_as_scoped().await; let handle = q.send(RoomMessageEventContent::text_plain("welcome to my ted talk").into()).await.unwrap(); @@ -1250,7 +1066,7 @@ async fn test_edit_wakes_the_sending_task() { // Now edit the event's content (imagine we make it "shorter"). drop(send_mock_scope); - mock_send_event(event_id!("$1")).mount(&server).await; + mock.mock_room_send().ok(event_id!("$1")).mount().await; let edited = handle .edit(RoomMessageEventContent::text_plain("here's the summary of my ted talk").into()) @@ -1267,21 +1083,13 @@ async fn test_edit_wakes_the_sending_task() { #[async_test] async fn test_abort_after_disable() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); + let room = mock.sync_joined_room(room_id).await; - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; - + let client = mock.client(); let mut errors = client.send_queue().subscribe_errors(); assert!(errors.is_empty()); @@ -1298,12 +1106,12 @@ async fn test_abort_after_disable() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - server.reset().await; + mock.verify_and_reset().await; - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; // Respond to /send with a transient 500 error. - mock_send_transient_failure().expect(3).mount(&server).await; + mock.mock_room_send().error500().expect(3).mount().await; // One message is queued. let handle = q.send(RoomMessageEventContent::text_plain("hey there").into()).await.unwrap(); @@ -1333,22 +1141,14 @@ async fn test_abort_after_disable() { #[async_test] async fn test_abort_or_edit_after_send() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; // Start with an enabled sending queue. + let client = mock.client(); client.send_queue().set_enabled(true).await; let q = room.send_queue(); @@ -1357,9 +1157,9 @@ async fn test_abort_or_edit_after_send() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - server.reset().await; - mock_encryption_state(&server, false).await; - mock_send_event(event_id!("$1")).mount(&server).await; + mock.verify_and_reset().await; + mock.mock_room_state_encryption().plain().mount().await; + mock.mock_room_send().ok(event_id!("$1")).mount().await; let handle = q.send(RoomMessageEventContent::text_plain("hey there").into()).await.unwrap(); @@ -1384,20 +1184,11 @@ async fn test_abort_or_edit_after_send() { #[async_test] async fn test_abort_while_being_sent_and_fails() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -1411,11 +1202,9 @@ async fn test_abort_while_being_sent_and_fails() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -1430,7 +1219,7 @@ async fn test_abort_while_being_sent_and_fails() { ResponseTemplate::new(500) }) .expect(3) // reattempts, because of short_retry() - .mount(&server) + .mount() .await; let handle = q.send(RoomMessageEventContent::text_plain("yo").into()).await.unwrap(); @@ -1463,21 +1252,13 @@ async fn test_abort_while_being_sent_and_fails() { #[async_test] async fn test_unrecoverable_errors() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); + let room = mock.sync_joined_room(room_id).await; - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; - + let client = mock.client(); let mut errors = client.send_queue().subscribe_errors(); assert!(errors.is_empty()); @@ -1494,33 +1275,14 @@ async fn test_unrecoverable_errors() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - server.reset().await; - - mock_encryption_state(&server, false).await; + mock.verify_and_reset().await; - let respond_with_unrecoverable = AtomicBool::new(true); + mock.mock_room_state_encryption().plain().mount().await; // Respond to the first /send with an unrecoverable error. - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(move |_req: &Request| { - // The first message gets M_TOO_LARGE, subsequent messages will encounter a - // great success. - if respond_with_unrecoverable.swap(false, Ordering::SeqCst) { - ResponseTemplate::new(413).set_body_json(json!({ - // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response - "errcode": "M_TOO_LARGE", - })) - } else { - ResponseTemplate::new(200).set_body_json(json!({ - "event_id": "$42", - })) - } - }) - .expect(2) - .mount(&server) - .await; + mock.mock_room_send().error_too_large().mock_once().mount().await; + // Respond to the second /send with an OK response. + mock.mock_room_send().ok(event_id!("$42")).mock_once().mount().await; // Queue two messages. q.send(RoomMessageEventContent::text_plain("i'm too big for ya").into()).await.unwrap(); @@ -1552,21 +1314,13 @@ async fn test_unrecoverable_errors() { #[async_test] async fn test_unwedge_unrecoverable_errors() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); + let room = mock.sync_joined_room(room_id).await; - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; - + let client = mock.client(); let mut errors = client.send_queue().subscribe_errors(); assert!(errors.is_empty()); @@ -1583,33 +1337,14 @@ async fn test_unwedge_unrecoverable_errors() { assert!(local_echoes.is_empty()); assert!(watch.is_empty()); - server.reset().await; - - mock_encryption_state(&server, false).await; + mock.verify_and_reset().await; - let respond_with_unrecoverable = AtomicBool::new(true); + mock.mock_room_state_encryption().plain().mount().await; // Respond to the first /send with an unrecoverable error. - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(move |_req: &Request| { - // The first message gets M_TOO_LARGE, subsequent messages will encounter a - // great success. - if respond_with_unrecoverable.swap(false, Ordering::SeqCst) { - ResponseTemplate::new(413).set_body_json(json!({ - // From https://spec.matrix.org/v1.10/client-server-api/#standard-error-response - "errcode": "M_TOO_LARGE", - })) - } else { - ResponseTemplate::new(200).set_body_json(json!({ - "event_id": "$42", - })) - } - }) - .expect(2) - .mount(&server) - .await; + mock.mock_room_send().error_too_large().mock_once().mount().await; + // Respond to the second /send with an OK response. + mock.mock_room_send().ok(event_id!("$42")).mock_once().mount().await; // Queue the unrecoverable message. q.send(RoomMessageEventContent::text_plain("i'm too big for ya").into()).await.unwrap(); @@ -1648,25 +1383,16 @@ async fn test_no_network_access_error_is_recoverable() { // server in a static. Using the line below will create a "bare" server, // which is effectively dropped upon `drop()`. let server = wiremock::MockServer::builder().start().await; - let client = logged_in_client(Some(server.uri().to_string())).await; + let mock = MatrixMockServer::from_pair(server, client.clone()); // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; // Dropping the server: any subsequent attempt to connect mimics an unreachable // server, which might be caused by missing network. - drop(server); + drop(mock); let mut errors = client.send_queue().subscribe_errors(); assert!(errors.is_empty()); @@ -1722,20 +1448,11 @@ async fn test_reloading_rooms_with_unsent_events() { .unwrap(); set_client_session(&client).await; - // Mark two rooms as joined. - let room = mock_sync_with_new_room( - |builder| { - builder - .add_joined_room(JoinedRoomBuilder::new(room_id)) - .add_joined_room(JoinedRoomBuilder::new(room_id2)); - }, - &client, - &server, - room_id, - ) - .await; + let mock = MatrixMockServer::from_pair(server, client.clone()); - let room2 = client.get_room(room_id2).unwrap(); + // Mark two rooms as joined. + let room = mock.sync_joined_room(room_id).await; + let room2 = mock.sync_joined_room(room_id2).await; // Globally disable the send queue. let q = client.send_queue(); @@ -1758,7 +1475,7 @@ async fn test_reloading_rooms_with_unsent_events() { sleep(Duration::from_millis(300)).await; assert!(watch.is_empty()); - server.reset().await; + mock.verify_and_reset().await; { // Kill the client, let it close background tasks. @@ -1771,26 +1488,13 @@ async fn test_reloading_rooms_with_unsent_events() { // Create a new client with the same memory backend. As the send queues are // enabled by default, it will respawn tasks for sending events to those two // rooms in the background. - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - let event_id = StdMutex::new(0); - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) - .respond_with(move |_req: &Request| { - let mut event_id_guard = event_id.lock().unwrap(); - let event_id = *event_id_guard; - *event_id_guard += 1; - ResponseTemplate::new(200).set_body_json(json!({ - "event_id": event_id - })) - }) - .expect(2) - .mount(&server) - .await; + mock.mock_room_send().ok(event_id!("$1")).mock_once().mount().await; + mock.mock_room_send().ok(event_id!("$2")).mock_once().mount().await; let client = Client::builder() - .homeserver_url(server.uri()) + .homeserver_url(mock.server().uri()) .server_versions([MatrixVersion::V1_0]) .store_config(StoreConfig::new().state_store(store)) .request_config(RequestConfig::new().disable_retry()) @@ -1805,25 +1509,16 @@ async fn test_reloading_rooms_with_unsent_events() { sleep(Duration::from_secs(1)).await; // The real assertion is on the expect(2) on the above Mock. - server.verify().await; + mock.verify_and_reset().await; } #[async_test] async fn test_reactions() { - let (client, server) = logged_in_client_with_server().await; + let mock = MatrixMockServer::new().await; // Mark the room as joined. let room_id = room_id!("!a:b.c"); - - let room = mock_sync_with_new_room( - |builder| { - builder.add_joined_room(JoinedRoomBuilder::new(room_id)); - }, - &client, - &server, - room_id, - ) - .await; + let room = mock.sync_joined_room(room_id).await; let q = room.send_queue(); @@ -1836,11 +1531,9 @@ async fn test_reactions() { let mock_lock = lock.clone(); - mock_encryption_state(&server, false).await; + mock.mock_room_state_encryption().plain().mount().await; - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) - .and(header("authorization", "Bearer 1234")) + mock.mock_room_send() .respond_with(move |_req: &Request| { // Wait for the signal from the main thread that we can process this query. let mock_lock = mock_lock.clone(); @@ -1860,18 +1553,12 @@ async fn test_reactions() { })) }) .expect(3) - .mount(&server) + .mount() .await; // Sending of the second emoji has started; abort it, it will result in a redact // request. - Mock::given(method("PUT")) - .and(path_regex(r"^/_matrix/client/r0/rooms/.*/redact/.*?/.*?")) - .and(header("authorization", "Bearer 1234")) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({"event_id": "$3"}))) - .expect(1) - .mount(&server) - .await; + mock.mock_room_redact().ok(event_id!("$3")).expect(1).mount().await; // Send a message. let msg_handle =