From 6f60eea9cecd4edb0c1a6b02d73e25ed96f138c6 Mon Sep 17 00:00:00 2001 From: Benjamin Bouvier Date: Thu, 7 Nov 2024 16:13:00 +0100 Subject: [PATCH] task(tests): refactor mock system to use generic endpoints and avoid code duplication --- crates/matrix-sdk/src/test_utils/mocks.rs | 209 +++++++++------------- 1 file changed, 87 insertions(+), 122 deletions(-) diff --git a/crates/matrix-sdk/src/test_utils/mocks.rs b/crates/matrix-sdk/src/test_utils/mocks.rs index 874568eef3f..3cd2af0123f 100644 --- a/crates/matrix-sdk/src/test_utils/mocks.rs +++ b/crates/matrix-sdk/src/test_utils/mocks.rs @@ -44,14 +44,14 @@ use crate::{Client, Room}; /// It works like this: /// /// - start by saying which endpoint you'd like to mock, e.g. -/// [`Self::mock_room_send()`]. This returns a specialized `MockSomething` +/// [`Self::mock_room_send()`]. This returns a specialized [`MockEndpoint`] /// data structure, with its own impl. For this example, it's -/// [`MockRoomSend`]. +/// `MockEndpoint`. /// - configure the response on the endpoint-specific mock data structure. For /// instance, if you want the sending to result in a transient failure, call -/// [`MockRoomSend::error500`]; if you want it to succeed and return the event -/// `$42`, call [`MockRoomSend::ok`]. It's still possible to call -/// [`MockRoomSend::respond_with()`], as we do with wiremock MockBuilder, for +/// [`MockEndpoint::error500`]; if you want it to succeed and return the event +/// `$42`, call [`MockEndpoint::ok()`]. It's still possible to call +/// [`MockEndpoint::respond_with()`], as we do with wiremock MockBuilder, for /// maximum flexibility when the helpers aren't sufficient. /// - once the endpoint's response is configured, for any mock builder, you get /// a [`MatrixMock`]; this is a plain [`wiremock::Mock`] with the server @@ -139,81 +139,85 @@ impl MatrixMockServer { // Specific mount endpoints. impl MatrixMockServer { /// Mocks a sync endpoint. - pub fn mock_sync(&self) -> MockSync<'_> { + pub fn mock_sync(&self) -> MockEndpoint<'_, SyncEndpoint> { let mock = Mock::given(method("GET")) .and(path("/_matrix/client/r0/sync")) .and(header("authorization", "Bearer 1234")); - MockSync { + MockEndpoint { mock, server: &self.server, - sync_response_builder: self.sync_response_builder.clone(), + endpoint: SyncEndpoint { 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<'_> { + pub fn mock_room_send(&self) -> MockEndpoint<'_, RoomSendEndpoint> { 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 } + MockEndpoint { mock, server: &self.server, endpoint: RoomSendEndpoint } } /// 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<'_> { + pub fn mock_room_state_encryption(&self) -> MockEndpoint<'_, EncryptionStateEndpoint> { 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 } + MockEndpoint { mock, server: &self.server, endpoint: EncryptionStateEndpoint } } /// Creates a prebuilt mock for setting the room encryption state. /// /// Note: Applies to all rooms. - pub fn mock_set_room_state_encryption(&self) -> MockSetEncryptionState<'_> { + pub fn mock_set_room_state_encryption(&self) -> MockEndpoint<'_, SetEncryptionStateEndpoint> { 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 } + MockEndpoint { mock, server: &self.server, endpoint: SetEncryptionStateEndpoint } } /// Creates a prebuilt mock for the room redact endpoint. - pub fn mock_room_redact(&self) -> MockRoomRedact<'_> { + pub fn mock_room_redact(&self) -> MockEndpoint<'_, RoomRedactEndpoint> { 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 } + MockEndpoint { mock, server: &self.server, endpoint: RoomRedactEndpoint } } /// Creates a prebuilt mock for retrieving an event with /room/.../event. - pub fn mock_room_event(&self) -> MockRoomEvent<'_> { + pub fn mock_room_event(&self) -> MockEndpoint<'_, RoomEventEndpoint> { let mock = Mock::given(method("GET")).and(header("authorization", "Bearer 1234")); - MockRoomEvent { mock, server: &self.server, room: None, match_event_id: false } + MockEndpoint { + mock, + server: &self.server, + endpoint: RoomEventEndpoint { room: None, match_event_id: false }, + } } /// Create a prebuilt mock for uploading media. - pub fn mock_upload(&self) -> MockUpload<'_> { + pub fn mock_upload(&self) -> MockEndpoint<'_, UploadEndpoint> { let mock = Mock::given(method("POST")) .and(path("/_matrix/media/r0/upload")) .and(header("authorization", "Bearer 1234")); - MockUpload { mock, server: &self.server } + MockEndpoint { mock, server: &self.server, endpoint: UploadEndpoint } } /// Create a prebuilt mock for resolving room aliases. - pub fn mock_room_directory_resolve_alias(&self) -> MockResolveRoomAlias<'_> { + pub fn mock_room_directory_resolve_alias(&self) -> MockEndpoint<'_, ResolveRoomAliasEndpoint> { let mock = Mock::given(method("GET")).and(path_regex(r"/_matrix/client/r0/directory/room/.*")); - MockResolveRoomAlias { mock, server: &self.server } + MockEndpoint { mock, server: &self.server, endpoint: ResolveRoomAliasEndpoint } } /// Create a prebuilt mock for creating room aliases. - pub fn mock_create_room_alias(&self) -> MockCreateRoomAlias<'_> { + pub fn mock_create_room_alias(&self) -> MockEndpoint<'_, CreateRoomAliasEndpoint> { let mock = Mock::given(method("PUT")).and(path_regex(r"/_matrix/client/r0/directory/room/.*")); - MockCreateRoomAlias { mock, server: &self.server } + MockEndpoint { mock, server: &self.server, endpoint: CreateRoomAliasEndpoint } } } @@ -325,13 +329,41 @@ impl<'a> MatrixMock<'a> { } } -/// A prebuilt mock for sending events to a room. -pub struct MockRoomSend<'a> { +/// Generic mocked endpoint, with useful common helpers. +pub struct MockEndpoint<'a, T> { server: &'a MockServer, mock: MockBuilder, + endpoint: T, +} + +impl<'a, T> MockEndpoint<'a, T> { + /// 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 } + } + + /// 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 } + } + + /// Internal helper to return an `{ event_id }` JSON struct along with a 200 + /// ok response. + fn ok_with_event_id(self, event_id: OwnedEventId) -> MatrixMock<'a> { + let mock = self.mock.respond_with( + ResponseTemplate::new(200).set_body_json(json!({ "event_id": event_id })), + ); + MatrixMock { server: self.server, mock } + } } -impl<'a> MockRoomSend<'a> { +/// A prebuilt mock for sending an event in a room. +pub struct RoomSendEndpoint; + +impl<'a> MockEndpoint<'a, RoomSendEndpoint> { /// 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 { @@ -341,19 +373,7 @@ 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 } + self.ok_with_event_id(returned_event_id.into()) } /// Returns a send endpoint that emulates a permanent failure (event is too @@ -367,30 +387,21 @@ impl<'a> MockRoomSend<'a> { server: self.server, } } - - /// 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 } - } } /// A prebuilt mock for running sync v2. -pub struct MockSync<'a> { - mock: MockBuilder, - server: &'a MockServer, +pub struct SyncEndpoint { sync_response_builder: Arc>, } -impl<'a> MockSync<'a> { +impl<'a> MockEndpoint<'a, SyncEndpoint> { /// 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, client: &Client, func: F) { let json_response = { - let mut builder = self.sync_response_builder.lock().unwrap(); + let mut builder = self.endpoint.sync_response_builder.lock().unwrap(); func(&mut builder); builder.build_json_sync_response() }; @@ -406,12 +417,9 @@ impl<'a> MockSync<'a> { } /// A prebuilt mock for reading the encryption state of a room. -pub struct MockEncryptionState<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct EncryptionStateEndpoint; -impl<'a> MockEncryptionState<'a> { +impl<'a> MockEndpoint<'a, EncryptionStateEndpoint> { /// Marks the room as encrypted. pub fn encrypted(self) -> MatrixMock<'a> { let mock = self.mock.respond_with( @@ -430,63 +438,49 @@ impl<'a> MockEncryptionState<'a> { } /// A prebuilt mock for setting the encryption state of a room. -pub struct MockSetEncryptionState<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct SetEncryptionStateEndpoint; -impl<'a> MockSetEncryptionState<'a> { +impl<'a> MockEndpoint<'a, SetEncryptionStateEndpoint> { /// 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 } + self.ok_with_event_id(returned_event_id.into()) } } /// A prebuilt mock for redacting an event in a room. -pub struct MockRoomRedact<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct RoomRedactEndpoint; -impl<'a> MockRoomRedact<'a> { +impl<'a> MockEndpoint<'a, RoomRedactEndpoint> { /// 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 } + self.ok_with_event_id(returned_event_id.into()) } } /// A prebuilt mock for getting a single event in a room. -pub struct MockRoomEvent<'a> { +pub struct RoomEventEndpoint { room: Option, match_event_id: bool, - server: &'a MockServer, - mock: MockBuilder, } -impl<'a> MockRoomEvent<'a> { +impl<'a> MockEndpoint<'a, RoomEventEndpoint> { /// Limits the scope of this mock to a specific room. - pub fn room(self, room: impl Into) -> Self { - Self { room: Some(room.into()), ..self } + pub fn room(mut self, room: impl Into) -> Self { + self.endpoint.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 } + pub fn match_event_id(mut self) -> Self { + self.endpoint.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_path = if self.endpoint.match_event_id { let event_id = event.kind.event_id().expect("an event id is required"); event_id.to_string() } else { @@ -494,7 +488,7 @@ impl<'a> MockRoomEvent<'a> { "".to_owned() }; - let room_path = self.room.map_or_else(|| ".*".to_owned(), |room| room.to_string()); + let room_path = self.endpoint.room.map_or_else(|| ".*".to_owned(), |room| room.to_string()); let mock = self .mock @@ -505,12 +499,9 @@ impl<'a> MockRoomEvent<'a> { } /// A prebuilt mock for uploading media. -pub struct MockUpload<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct UploadEndpoint; -impl<'a> MockUpload<'a> { +impl<'a> MockEndpoint<'a, UploadEndpoint> { /// 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 } @@ -524,29 +515,12 @@ impl<'a> MockUpload<'a> { }))); MatrixMock { server: self.server, mock } } - - /// Returns a send endpoint that emulates a transient failure, i.e responds - /// with error 500. - pub fn error500(self) -> MatrixMock<'a> { - let mock = self.mock.respond_with(ResponseTemplate::new(500)); - 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 } - } } /// A prebuilt mock for resolving a room alias. -pub struct MockResolveRoomAlias<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct ResolveRoomAliasEndpoint; -impl<'a> MockResolveRoomAlias<'a> { +impl<'a> MockEndpoint<'a, ResolveRoomAliasEndpoint> { /// Returns a data endpoint with a resolved room alias. pub fn ok(self, room_id: &str, servers: Vec) -> MatrixMock<'a> { let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({ @@ -564,21 +538,12 @@ impl<'a> MockResolveRoomAlias<'a> { }))); MatrixMock { server: self.server, mock } } - - /// Returns a data endpoint with a server error. - pub fn error500(self) -> MatrixMock<'a> { - let mock = self.mock.respond_with(ResponseTemplate::new(500)); - MatrixMock { server: self.server, mock } - } } /// A prebuilt mock for creating a room alias. -pub struct MockCreateRoomAlias<'a> { - server: &'a MockServer, - mock: MockBuilder, -} +pub struct CreateRoomAliasEndpoint; -impl<'a> MockCreateRoomAlias<'a> { +impl<'a> MockEndpoint<'a, CreateRoomAliasEndpoint> { /// Returns a data endpoint for creating a room alias. pub fn ok(self) -> MatrixMock<'a> { let mock = self.mock.respond_with(ResponseTemplate::new(200).set_body_json(json!({})));