From 4dbc0aaad65c571f7356ebae15acdfe07e74c87e Mon Sep 17 00:00:00 2001 From: Devin R Date: Tue, 7 Apr 2020 08:55:42 -0400 Subject: [PATCH] clean up, cargo fmt/clippy, add example/TestRunner tests --- src/async_client.rs | 92 ++++++-- src/base_client.rs | 95 +++++++- src/lib.rs | 1 - src/models/room.rs | 34 +-- src/models/room_member.rs | 41 +++- src/test_builder.rs | 471 ++++++++++++++++++++++++++++---------- 6 files changed, 564 insertions(+), 170 deletions(-) diff --git a/src/async_client.rs b/src/async_client.rs index dc3ae4ad..4944df91 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -393,15 +393,13 @@ impl AsyncClient { for account_data in &mut room.account_data.events { { if let EventResult::Ok(e) = account_data { - client.receive_account_data(&room_id, e).await; + client.receive_account_data_event(&room_id, e).await; client.emit_account_data_event(room_id, e).await; } } } - // TODO `IncomingEphemeral` events for typing events - // After the room has been created and state/timeline events accounted for we use the room_id of the newly created // room to add any presence events that relate to a user in the current room. This is not super // efficient but we need a room_id so we would loop through now or later. @@ -414,6 +412,16 @@ impl AsyncClient { } } } + + for ephemeral in &mut room.ephemeral.events { + { + if let EventResult::Ok(e) = ephemeral { + client.receive_ephemeral_event(&room_id, e).await; + + client.emit_ephemeral_event(room_id, e).await; + } + } + } } let mut client = self.base_client.write().await; @@ -793,39 +801,85 @@ impl AsyncClient { #[cfg(test)] mod test { - use super::{AsyncClient, Room, Url}; - use crate::identifiers::{RoomId, UserId}; + use super::{AsyncClient, Url}; use crate::events::collections::all::RoomEvent; + use crate::identifiers::{RoomId, UserId}; - use crate::test_builder::{EventBuilder}; + use crate::test_builder::EventBuilder; use crate::{assert_eq_, async_assert}; use std::convert::TryFrom; #[tokio::test] - async fn client_runner() { // TODO make this actually test something + async fn client_runner() { + // TODO make this actually test something - async_assert!{ - async fn test_room_users<'a>(cli: &'a AsyncClient) -> Result<(), String> { - assert_eq_!(cli.homeserver(), &Url::parse("http://localhost:8080").unwrap()); + async_assert! { + async fn test_client_homeserver<'a>(cli: &'a AsyncClient) -> Result<(), String> { + assert_eq_!(cli.homeserver(), &Url::parse(&mockito::server_url()).unwrap()); Ok(()) } } + let session = crate::Session { + access_token: "12345".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); - let homeserver = Url::parse("http://localhost:8080").unwrap(); - let client = AsyncClient::new(homeserver, None).unwrap(); - let room = Room::new(&rid, &uid); let bld = EventBuilder::default(); - let runner = bld.add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) - .build_client_runner("!roomid:room.com", "@example:localhost") - .set_room(room) + let runner = bld + .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) + .add_room_event_from_file( + "./tests/data/events/power_levels.json", + RoomEvent::RoomPowerLevels, + ) + .build_client_runner(rid, uid) .set_client(client) - .add_client_assert(test_room_users); - + .add_client_assert(test_client_homeserver); + + runner.run_test().await; + } + + #[tokio::test] + async fn mock_runner() { + use std::convert::TryFrom; + + async_assert! { + async fn test_mock_homeserver<'a>(cli: &'a AsyncClient) -> Result<(), String> { + assert_eq_!(cli.homeserver(), &url::Url::parse(&mockito::server_url()).unwrap()); + Ok(()) + } + } + + let session = crate::Session { + access_token: "12345".to_owned(), + user_id: UserId::try_from("@example:localhost").unwrap(), + device_id: "DEVICEID".to_owned(), + }; + + let homeserver = url::Url::parse(&mockito::server_url()).unwrap(); + let client = AsyncClient::new(homeserver, Some(session)).unwrap(); + + let bld = EventBuilder::default(); + let runner = bld + .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) + .add_room_event_from_file( + "./tests/data/events/power_levels.json", + RoomEvent::RoomPowerLevels, + ) + .build_mock_runner( + "GET", + mockito::Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()), + ) + .set_client(client) + .add_client_assert(test_mock_homeserver); + runner.run_test().await; } } diff --git a/src/base_client.rs b/src/base_client.rs index 5bbcff52..1844d431 100644 --- a/src/base_client.rs +++ b/src/base_client.rs @@ -306,17 +306,42 @@ impl Client { } } - /// Receive a presence event from a sync response and updates the client state. + /// Receive an account data event from a sync response and updates the client state. /// - /// This will only update the user if found in the current room looped through by `AsyncClient::sync`. - /// Returns true if the specific users presence has changed, false otherwise. + /// Returns true if the state of the `Room` has changed, false otherwise. /// /// # Arguments /// /// * `room_id` - The unique id of the room the event belongs to. /// /// * `event` - The presence event for a specified room member. - pub async fn receive_account_data(&mut self, room_id: &RoomId, event: &NonRoomEvent) -> bool { + pub async fn receive_account_data_event( + &mut self, + room_id: &RoomId, + event: &NonRoomEvent, + ) -> bool { + match event { + NonRoomEvent::IgnoredUserList(iu) => self.handle_ignored_users(iu), + NonRoomEvent::Presence(p) => self.receive_presence_event(room_id, p).await, + NonRoomEvent::PushRules(pr) => self.handle_push_rules(pr), + _ => false, + } + } + + /// Receive an ephemeral event from a sync response and updates the client state. + /// + /// Returns true if the state of the `Room` has changed, false otherwise. + /// + /// # Arguments + /// + /// * `room_id` - The unique id of the room the event belongs to. + /// + /// * `event` - The presence event for a specified room member. + pub async fn receive_ephemeral_event( + &mut self, + room_id: &RoomId, + event: &NonRoomEvent, + ) -> bool { match event { NonRoomEvent::IgnoredUserList(iu) => self.handle_ignored_users(iu), NonRoomEvent::Presence(p) => self.receive_presence_event(room_id, p).await, @@ -754,6 +779,68 @@ impl Client { } } + pub(crate) async fn emit_ephemeral_event( + &mut self, + room_id: &RoomId, + event: &mut NonRoomEvent, + ) { + match event { + NonRoomEvent::Presence(presence) => { + if let Some(ee) = &self.event_emitter { + if let Some(room) = self.get_room(&room_id) { + ee.lock() + .await + .on_account_presence( + Arc::clone(&room), + Arc::new(Mutex::new(presence.clone())), + ) + .await; + } + } + } + NonRoomEvent::IgnoredUserList(ignored) => { + if let Some(ee) = &self.event_emitter { + if let Some(room) = self.get_room(&room_id) { + ee.lock() + .await + .on_account_ignored_users( + Arc::clone(&room), + Arc::new(Mutex::new(ignored.clone())), + ) + .await; + } + } + } + NonRoomEvent::PushRules(rules) => { + if let Some(ee) = &self.event_emitter { + if let Some(room) = self.get_room(&room_id) { + ee.lock() + .await + .on_account_push_rules( + Arc::clone(&room), + Arc::new(Mutex::new(rules.clone())), + ) + .await; + } + } + } + NonRoomEvent::FullyRead(full_read) => { + if let Some(ee) = &self.event_emitter { + if let Some(room) = self.get_room(&room_id) { + ee.lock() + .await + .on_account_data_fully_read( + Arc::clone(&room), + Arc::new(Mutex::new(full_read.clone())), + ) + .await; + } + } + } + _ => {} + } + } + pub(crate) async fn emit_presence_event( &mut self, room_id: &RoomId, diff --git a/src/lib.rs b/src/lib.rs index 69c132ef..28bf9375 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,6 @@ //! by default be stored only in memory and thus lost after the client is //! destroyed. #![deny(missing_docs)] -#![feature(type_alias_impl_trait)] pub use crate::{error::Error, error::Result, session::Session}; pub use reqwest::header::InvalidHeaderValue; diff --git a/src/models/room.rs b/src/models/room.rs index 2549120c..cfe40178 100644 --- a/src/models/room.rs +++ b/src/models/room.rs @@ -342,7 +342,7 @@ impl Room { /// Receive a state event for this room and update the room state. /// - /// Returns true if the joined member list changed, false otherwise. + /// Returns true if the state of the `Room` has changed, false otherwise. /// /// # Arguments /// @@ -388,9 +388,9 @@ mod test { use super::*; use crate::events::room::member::MembershipState; use crate::identifiers::UserId; - use crate::{AsyncClient, Session, SyncSettings}; - use crate::test_builder::{EventBuilder}; + use crate::test_builder::EventBuilder; use crate::{assert_, assert_eq_}; + use crate::{AsyncClient, Session, SyncSettings}; use mockito::{mock, Matcher}; use url::Url; @@ -400,11 +400,6 @@ mod test { use std::str::FromStr; use std::time::Duration; - #[tokio::test] - async fn add_member() { - - } - #[tokio::test] async fn user_presence() { let homeserver = Url::from_str(&mockito::server_url()).unwrap(); @@ -446,16 +441,21 @@ mod test { #[tokio::test] async fn room_events() { - fn test_room_users(room: &Room) -> Result<(), String> { assert_eq_!(room.members.len(), 1); Ok(()) } - + fn test_room_power(room: &Room) -> Result<(), String> { assert_!(room.power_levels.is_some()); - assert_eq_!(room.power_levels.as_ref().unwrap().kick, js_int::Int::new(50).unwrap()); - let admin = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap(); + assert_eq_!( + room.power_levels.as_ref().unwrap().kick, + js_int::Int::new(50).unwrap() + ); + let admin = room + .members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); assert_eq_!(admin.power_level.unwrap(), js_int::Int::new(100).unwrap()); Ok(()) } @@ -463,12 +463,16 @@ mod test { let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); let bld = EventBuilder::default(); - let runner = bld.add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) + let runner = bld + .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) + .add_room_event_from_file( + "./tests/data/events/power_levels.json", + RoomEvent::RoomPowerLevels, + ) .build_room_runner(&rid, &uid) .add_room_assert(test_room_power) .add_room_assert(test_room_users); - + runner.run_test().await; } } diff --git a/src/models/room_member.rs b/src/models/room_member.rs index e2ef43d0..ce905cf5 100644 --- a/src/models/room_member.rs +++ b/src/models/room_member.rs @@ -184,11 +184,11 @@ impl RoomMember { #[cfg(test)] mod test { - use crate::events::room::member::MembershipState; use crate::events::collections::all::RoomEvent; use crate::events::presence::PresenceState; + use crate::events::room::member::MembershipState; use crate::identifiers::{RoomId, UserId}; - use crate::test_builder::{EventBuilder}; + use crate::test_builder::EventBuilder; use crate::{assert_, assert_eq_, Room}; use js_int::{Int, UInt}; @@ -198,7 +198,10 @@ mod test { #[tokio::test] async fn room_member_events() { fn test_room_member(room: &Room) -> Result<(), String> { - let member = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap(); + let member = room + .members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); assert_eq_!(member.membership, MembershipState::Join); assert_eq_!(member.power_level, Int::new(100)); println!("{:#?}", room); @@ -208,26 +211,36 @@ mod test { let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); let bld = EventBuilder::default(); - let runner = bld.add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) + let runner = bld + .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) + .add_room_event_from_file( + "./tests/data/events/power_levels.json", + RoomEvent::RoomPowerLevels, + ) .build_room_runner(&rid, &uid) .add_room_assert(test_room_member); - + runner.run_test().await; } #[tokio::test] async fn member_presence_events() { fn test_room_member(room: &Room) -> Result<(), String> { - let member = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap(); + let member = room + .members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); assert_eq_!(member.membership, MembershipState::Join); assert_eq_!(member.power_level, Int::new(100)); println!("{:#?}", room); Ok(()) } - + fn test_presence(room: &Room) -> Result<(), String> { - let member = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap(); + let member = room + .members + .get(&UserId::try_from("@example:localhost").unwrap()) + .unwrap(); assert_!(member.avatar_url.is_some()); assert_eq_!(member.last_active_ago, UInt::new(1)); assert_eq_!(member.presence, Some(PresenceState::Online)); @@ -237,13 +250,17 @@ mod test { let rid = RoomId::try_from("!roomid:room.com").unwrap(); let uid = UserId::try_from("@example:localhost").unwrap(); let bld = EventBuilder::default(); - let runner = bld.add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) - .add_room_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) + let runner = bld + .add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) + .add_room_event_from_file( + "./tests/data/events/power_levels.json", + RoomEvent::RoomPowerLevels, + ) .add_presence_event_from_file("./tests/data/events/presence.json") .build_room_runner(&rid, &uid) .add_room_assert(test_presence) .add_room_assert(test_room_member); - + runner.run_test().await; } } diff --git a/src/test_builder.rs b/src/test_builder.rs index dbe6e1e3..aada6502 100644 --- a/src/test_builder.rs +++ b/src/test_builder.rs @@ -1,41 +1,45 @@ #![cfg(test)] use std::fs; -use std::path::Path; use std::panic; +use std::path::Path; -use crate::identifiers::{RoomId, UserId}; use crate::events::{ - collections::all::{Event, RoomEvent, StateEvent}, + collections::{ + all::{RoomEvent, StateEvent}, + only::Event, + }, presence::PresenceEvent, EventResult, TryFromRaw, }; -use crate::{AsyncClient}; +use crate::identifiers::{RoomId, UserId}; +use crate::AsyncClient; use ansi_term::Colour; -use mockito::mock; +use mockito::{self, mock, Mock}; use crate::models::Room; /// `assert` to use in `TestRunner`. -/// +/// /// This returns an `Err` on failure, instead of panicking. #[macro_export] macro_rules! assert_ { ($truth:expr) => { if !$truth { - return Err(format!(r#"assertion failed: `(left == right)` + return Err(format!( + r#"assertion failed: `(left == right)` expression: `{:?}` - failed at {}"#, + failed at {}"#, stringify!($truth), file!(), - )) + )); } }; } /// `assert_eq` to use in `TestRunner. -/// +/// /// This returns an `Err` on failure, instead of panicking. #[macro_export] macro_rules! assert_eq_ { @@ -80,7 +84,7 @@ macro_rules! assert_eq_ { } /// `assert_ne` to use in `TestRunner. -/// +/// /// This returns an `Err` on failure, instead of panicking. #[macro_export] macro_rules! assert_ne_ { @@ -127,14 +131,14 @@ macro_rules! assert_ne_ { /// Convenience macro for declaring an `async` assert function to store in the `TestRunner`. /// /// Declares an async function that can be stored in a struct. -/// +/// /// # Examples /// ```rust /// # use matrix_sdk::AsyncClient; -/// # use url::Url; -/// async_assert!{ +/// # use url::Url; +/// async_assert!{ /// async fn foo(cli: &AsyncClient) -> Result<(), String> { -/// assert_eq_!(cli.homeserver(), Url::new("matrix.org")) +/// assert_eq_!(cli.homeserver(), &url::Url::parse("matrix.org").unwrap()); /// Ok(()) /// } /// } @@ -186,20 +190,44 @@ pub struct EventBuilder { presence_events: Vec, /// The state events that determine the state of a `Room`. state_events: Vec, - /// The state events that determine the state of a `Room`. - non_room_events: Vec, + /// The ephemeral room events that determine the state of a `Room`. + ephemeral: Vec, + /// The account data events that determine the state of a `Room`. + account_data: Vec, } -#[allow(dead_code)] -pub struct TestRunner { - /// Used when testing the whole client - client: Option, +pub struct RoomTestRunner { /// Used To test the models room: Option, - /// The non room events that determine the state of a `Room`. + /// The ephemeral room events that determine the state of a `Room`. + ephemeral: Vec, + /// The account data events that determine the state of a `Room`. + account_data: Vec, + /// The events that determine the state of a `Room`. /// - /// These are ephemeral and account events. - non_room_events: Vec, + /// When testing the models `RoomEvent`s are needed. + room_events: Vec, + /// The presence events that determine the presence state of a `RoomMember`. + presence_events: Vec, + /// The state events that determine the state of a `Room`. + state_events: Vec, + /// A `Vec` of callbacks that should assert something about the room. + /// + /// The callback should use the provided `assert_`, `assert_*_` macros. + room_assertions: Vec Result<(), String>>, +} + +pub struct ClientTestRunner { + /// Used when testing the whole client + client: Option, + /// RoomId and UserId to use for the events. + /// + /// The RoomId must match the RoomId of the events to track. + room_user_id: (RoomId, UserId), + /// The ephemeral room events that determine the state of a `Room`. + ephemeral: Vec, + /// The account data events that determine the state of a `Room`. + account_data: Vec, /// The events that determine the state of a `Room`. /// /// When testing the models `RoomEvent`s are needed. @@ -209,13 +237,31 @@ pub struct TestRunner { /// The state events that determine the state of a `Room`. state_events: Vec, /// A `Vec` of callbacks that should assert something about the client. - /// + /// /// The callback should use the provided `assert_`, `assert_*_` macros. client_assertions: Vec, - /// A `Vec` of callbacks that should assert something about the room. - /// +} +// the compiler complains that the event Vec's are never used they are. +#[allow(dead_code)] +pub struct MockTestRunner { + /// Used when testing the whole client + client: Option, + /// The ephemeral room events that determine the state of a `Room`. + ephemeral: Vec, + /// The account data events that determine the state of a `Room`. + account_data: Vec, + /// The events that determine the state of a `Room`. + /// + /// When testing the models `RoomEvent`s are needed. + room_events: Vec, + /// The presence events that determine the presence state of a `RoomMember`. + presence_events: Vec, + /// The state events that determine the state of a `Room`. + state_events: Vec, + /// A `Vec` of callbacks that should assert something about the client. + /// /// The callback should use the provided `assert_`, `assert_*_` macros. - room_assertions: Vec Result<(), String>>, + client_assertions: Vec, /// `mokito::Mock` mock: Option, } @@ -223,166 +269,227 @@ pub struct TestRunner { #[allow(dead_code)] #[allow(unused_mut)] impl EventBuilder { - /// Creates an `IncomingResponse` to hold events for a sync. pub fn create_sync_response(mut self) -> Self { - self } /// Just throw events at the client, not part of a specific response. pub fn create_event_stream(mut self) -> Self { - self } /// Add an event to the room events `Vec`. - pub fn add_non_event_from_file>(mut self, path: P, variant: fn(Ev) -> Event) -> Self { - let val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val).unwrap().into_result().unwrap(); - self.non_room_events.push(variant(event)); + pub fn add_ephemeral_from_file>( + mut self, + path: P, + variant: fn(Ev) -> Event, + ) -> Self { + let val = fs::read_to_string(path.as_ref()) + .expect(&format!("file not found {:?}", path.as_ref())); + let event = serde_json::from_str::>(&val) + .unwrap() + .into_result() + .unwrap(); + self.ephemeral.push(variant(event)); self } /// Add an event to the room events `Vec`. - pub fn add_room_event_from_file>(mut self, path: P, variant: fn(Ev) -> RoomEvent) -> Self { - let val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val).unwrap().into_result().unwrap(); + pub fn add_account_from_file>( + mut self, + path: P, + variant: fn(Ev) -> Event, + ) -> Self { + let val = fs::read_to_string(path.as_ref()) + .expect(&format!("file not found {:?}", path.as_ref())); + let event = serde_json::from_str::>(&val) + .unwrap() + .into_result() + .unwrap(); + self.account_data.push(variant(event)); + self + } + + /// Add an event to the room events `Vec`. + pub fn add_room_event_from_file>( + mut self, + path: P, + variant: fn(Ev) -> RoomEvent, + ) -> Self { + let val = fs::read_to_string(path.as_ref()) + .expect(&format!("file not found {:?}", path.as_ref())); + let event = serde_json::from_str::>(&val) + .unwrap() + .into_result() + .unwrap(); self.room_events.push(variant(event)); self } /// Add a state event to the state events `Vec`. - pub fn add_state_event_from_file>(mut self, path: P, variant: fn(Ev) -> StateEvent) -> Self { - let val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val).unwrap().into_result().unwrap(); + pub fn add_state_event_from_file>( + mut self, + path: P, + variant: fn(Ev) -> StateEvent, + ) -> Self { + let val = fs::read_to_string(path.as_ref()) + .expect(&format!("file not found {:?}", path.as_ref())); + let event = serde_json::from_str::>(&val) + .unwrap() + .into_result() + .unwrap(); self.state_events.push(variant(event)); self } /// Add a presence event to the presence events `Vec`. pub fn add_presence_event_from_file>(mut self, path: P) -> Self { - let val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref())); - let event = serde_json::from_str::>(&val).unwrap().into_result().unwrap(); + let val = fs::read_to_string(path.as_ref()) + .expect(&format!("file not found {:?}", path.as_ref())); + let event = serde_json::from_str::>(&val) + .unwrap() + .into_result() + .unwrap(); self.presence_events.push(event); self } /// Consumes `ResponseBuilder and returns a `TestRunner`. - /// + /// /// The `TestRunner` streams the events to the client and holds methods to make assertions /// about the state of the client. - pub fn build_client_runner(mut self, method: &str, path: &str) -> TestRunner { - // TODO serialize this properly - let body = serde_json::to_string(&self.room_events).unwrap(); - let mock = Some(mock(method, path) - .with_status(200) - .with_body(body) - .create()); - - TestRunner { + pub fn build_mock_runner>( + mut self, + method: &str, + path: P, + ) -> MockTestRunner { + let body = serde_json::json! { + { + "device_one_time_keys_count": {}, + "next_batch": "s526_47314_0_7_1_1_1_11444_1", + "device_lists": { + "changed": [], + "left": [] + }, + "rooms": { + "invite": {}, + "join": { + "!SVkFJHzfwvuaIEawgC:localhost": { + "summary": {}, + "account_data": { + "events": self.account_data + }, + "ephemeral": { + "events": self.ephemeral + }, + "state": { + "events": self.state_events + }, + "timeline": { + "events": self.room_events, + "limited": true, + "prev_batch": "t392-516_47314_0_7_1_1_1_11444_1" + }, + "unread_notifications": { + "highlight_count": 0, + "notification_count": 11 + } + } + }, + "leave": {} + }, + "to_device": { + "events": [] + }, + "presence": { + "events": [] + } + } + }; + let mock = Some( + mock(method, path) + .with_status(200) + .with_body(body.to_string()) + .create(), + ); + MockTestRunner { client: None, - room: None, - non_room_events: Vec::new(), + ephemeral: Vec::new(), + account_data: Vec::new(), room_events: Vec::new(), presence_events: Vec::new(), state_events: Vec::new(), client_assertions: Vec::new(), - room_assertions: Vec::new(), mock, } } /// Consumes `ResponseBuilder and returns a `TestRunner`. - /// - /// The `TestRunner` streams the events to the `Room` and holds methods to make assertions - /// about the state of the `Room`. - pub fn build_room_runner(self, room_id: &RoomId, user_id: &UserId) -> TestRunner { - TestRunner { + /// + /// The `TestRunner` streams the events to the `AsyncClient` and holds methods to make assertions + /// about the state of the `AsyncClient`. + pub fn build_client_runner(self, room_id: RoomId, user_id: UserId) -> ClientTestRunner { + ClientTestRunner { client: None, - room: Some(Room::new(room_id, user_id)), - non_room_events: self.non_room_events, + room_user_id: (room_id, user_id), + ephemeral: self.ephemeral, + account_data: self.account_data, room_events: self.room_events, presence_events: self.presence_events, state_events: self.state_events, client_assertions: Vec::new(), + } + } + + /// Consumes `ResponseBuilder and returns a `TestRunner`. + /// + /// The `TestRunner` streams the events to the `Room` and holds methods to make assertions + /// about the state of the `Room`. + pub fn build_room_runner(self, room_id: &RoomId, user_id: &UserId) -> RoomTestRunner { + RoomTestRunner { + room: Some(Room::new(room_id, user_id)), + ephemeral: self.ephemeral, + account_data: self.account_data, + room_events: self.room_events, + presence_events: self.presence_events, + state_events: self.state_events, room_assertions: Vec::new(), - mock: None, } } } -#[allow(dead_code)] -impl TestRunner { - pub fn set_client(mut self, client: AsyncClient) -> Self { - self.client = Some(client); - self - } - +impl RoomTestRunner { /// Set `Room` pub fn set_room(mut self, room: Room) -> Self { self.room = Some(room); self } - pub fn add_client_assert(mut self, assert: AsyncAssert) -> Self { - self.client_assertions.push(assert); - self - } - pub fn add_room_assert(mut self, assert: fn(&Room) -> Result<(), String>) -> Self { self.room_assertions.push(assert); self } - async fn run_client_tests(&mut self) -> Result<(), Vec> { - let mut errs = Vec::new(); - let mut cli = self.client.as_ref().unwrap().base_client.write().await; - let room_id = &self.room.as_ref().unwrap().room_id; - - for event in &self.non_room_events { - match event { - // Event::IgnoredUserList(iu) => room.handle_ignored_users(iu), - Event::Presence(p) => cli.receive_presence_event(room_id, p).await, - // Event::PushRules(pr) => room.handle_push_rules(pr), - // TODO receive ephemeral events - _ => todo!("implement more non room events"), - }; - } - - for event in &self.room_events { - cli.receive_joined_timeline_event(room_id, &mut EventResult::Ok(event.clone())).await; - } - for event in &self.presence_events { - cli.receive_presence_event(room_id, event).await; - } - for event in &self.state_events { - cli.receive_joined_state_event(room_id, event).await; - } - - for assert in &mut self.client_assertions { - if let Err(e) = assert(self.client.as_ref().unwrap()).await { - errs.push(e); - } - } - if errs.is_empty() { - Ok(()) - } else { - Err(errs) - } - } - fn run_room_tests(&mut self) -> Result<(), Vec> { let mut errs = Vec::new(); let room = self.room.as_mut().unwrap(); - for event in &self.non_room_events { + for event in &self.account_data { match event { // Event::IgnoredUserList(iu) => room.handle_ignored_users(iu), Event::Presence(p) => room.receive_presence_event(p), // Event::PushRules(pr) => room.handle_push_rules(pr), - // TODO receive ephemeral events - _ => todo!("implement more non room events"), + _ => todo!("implement more account data events"), + }; + } + + for event in &self.ephemeral { + match event { + // Event::IgnoredUserList(iu) => room.handle_ignored_users(iu), + Event::Presence(p) => room.receive_presence_event(p), + // Event::PushRules(pr) => room.handle_push_rules(pr), + _ => todo!("implement more account data events"), }; } @@ -411,7 +518,76 @@ impl TestRunner { pub async fn run_test(mut self) { let (count, errs) = if let Some(_) = &self.room { (self.room_assertions.len(), self.run_room_tests()) - } else if let Some(_) = &self.client { + } else { + panic!("must have either AsyncClient or Room") + }; + + if let Err(errs) = errs { + let err_str = errs.join(&format!("\n\n")); + eprintln!("{}\n{}", Colour::Red.paint("Error: "), err_str); + if !errs.is_empty() { + panic!("{} tests failed", errs.len()); + } else { + println!("{}. {} passed", Colour::Green.paint("Ok"), count); + } + } + } +} + +impl ClientTestRunner { + pub fn set_client(mut self, client: AsyncClient) -> Self { + self.client = Some(client); + self + } + + pub fn add_client_assert(mut self, assert: AsyncAssert) -> Self { + self.client_assertions.push(assert); + self + } + + async fn run_client_tests(&mut self) -> Result<(), Vec> { + let mut errs = Vec::new(); + let mut cli = self.client.as_ref().unwrap().base_client.write().await; + let room_id = &self.room_user_id.0; + + for event in &self.account_data { + match event { + // Event::IgnoredUserList(iu) => room.handle_ignored_users(iu), + Event::Presence(p) => cli.receive_presence_event(room_id, p).await, + // Event::PushRules(pr) => room.handle_push_rules(pr), + _ => todo!("implement more account data events"), + }; + } + + for event in &self.ephemeral { + cli.receive_ephemeral_event(room_id, event).await; + } + + for event in &self.room_events { + cli.receive_joined_timeline_event(room_id, &mut EventResult::Ok(event.clone())) + .await; + } + for event in &self.presence_events { + cli.receive_presence_event(room_id, event).await; + } + for event in &self.state_events { + cli.receive_joined_state_event(room_id, event).await; + } + + for assert in &mut self.client_assertions { + if let Err(e) = assert(self.client.as_ref().unwrap()).await { + errs.push(e); + } + } + if errs.is_empty() { + Ok(()) + } else { + Err(errs) + } + } + + pub async fn run_test(mut self) { + let (count, errs) = if let Some(_) = &self.client { (self.client_assertions.len(), self.run_client_tests().await) } else { panic!("must have either AsyncClient or Room") @@ -419,11 +595,68 @@ impl TestRunner { if let Err(errs) = errs { let err_str = errs.join(&format!("\n\n")); - println!("{}\n{}", Colour::Red.paint("Error: "), err_str); + eprintln!("{}\n{}", Colour::Red.paint("Error: "), err_str); if !errs.is_empty() { panic!("{} tests failed", errs.len()); } else { - eprintln!("{}. {} passed", Colour::Green.paint("Ok"), count); + println!("{}. {} passed", Colour::Green.paint("Ok"), count); + } + } + } +} + +impl MockTestRunner { + pub fn set_client(mut self, client: AsyncClient) -> Self { + self.client = Some(client); + self + } + + pub fn set_mock(mut self, mock: Mock) -> Self { + self.mock = Some(mock); + self + } + + pub fn add_client_assert(mut self, assert: AsyncAssert) -> Self { + self.client_assertions.push(assert); + self + } + + async fn run_mock_tests(&mut self) -> Result<(), Vec> { + let mut errs = Vec::new(); + self.client + .as_mut() + .unwrap() + .sync(crate::SyncSettings::default()) + .await + .map(|_r| ()) + .map_err(|e| vec![e.to_string()])?; + + for assert in &mut self.client_assertions { + if let Err(e) = assert(self.client.as_ref().unwrap()).await { + errs.push(e); + } + } + if errs.is_empty() { + Ok(()) + } else { + Err(errs) + } + } + + pub async fn run_test(mut self) { + let (count, errs) = if let Some(_) = &self.mock { + (self.client_assertions.len(), self.run_mock_tests().await) + } else { + panic!("must have either AsyncClient or Room") + }; + + if let Err(errs) = errs { + let err_str = errs.join(&format!("\n\n")); + eprintln!("{}\n{}", Colour::Red.paint("Error: "), err_str); + if !errs.is_empty() { + panic!("{} tests failed", errs.len()); + } else { + println!("{}. {} passed", Colour::Green.paint("Ok"), count); } } }