more work needed for AsyncClient test runner

master
Devin R 2020-04-06 20:59:44 -04:00
parent 78f92130fa
commit 9ffe7cca10
6 changed files with 558 additions and 283 deletions

View File

@ -790,3 +790,42 @@ impl AsyncClient {
Ok(response) Ok(response)
} }
} }
#[cfg(test)]
mod test {
use super::{AsyncClient, Room, Url};
use crate::identifiers::{RoomId, UserId};
use crate::events::collections::all::RoomEvent;
use crate::test_builder::{EventBuilder};
use crate::{assert_eq_, async_assert};
use std::convert::TryFrom;
#[tokio::test]
async fn room_events() {
async_assert!{
async fn test_room_users<'a>(cli: &'a AsyncClient) -> Result<(), String> {
assert_eq_!(cli.homeserver(), &Url::parse("http://localhost:8080").unwrap());
Ok(())
}
}
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)
.set_client(client)
.add_client_assert(test_room_users);
runner.run_test().await;
}
}

View File

@ -25,6 +25,7 @@
//! by default be stored only in memory and thus lost after the client is //! by default be stored only in memory and thus lost after the client is
//! destroyed. //! destroyed.
#![deny(missing_docs)] #![deny(missing_docs)]
#![feature(type_alias_impl_trait)]
pub use crate::{error::Error, error::Result, session::Session}; pub use crate::{error::Error, error::Result, session::Session};
pub use reqwest::header::InvalidHeaderValue; pub use reqwest::header::InvalidHeaderValue;
@ -40,8 +41,8 @@ mod event_emitter;
mod models; mod models;
mod session; mod session;
// TODO remove #[cfg(test)]
mod test_builder; pub mod test_builder;
#[cfg(feature = "encryption")] #[cfg(feature = "encryption")]
mod crypto; mod crypto;

View File

@ -389,6 +389,8 @@ mod test {
use crate::events::room::member::MembershipState; use crate::events::room::member::MembershipState;
use crate::identifiers::UserId; use crate::identifiers::UserId;
use crate::{AsyncClient, Session, SyncSettings}; use crate::{AsyncClient, Session, SyncSettings};
use crate::test_builder::{EventBuilder};
use crate::{assert_, assert_eq_};
use mockito::{mock, Matcher}; use mockito::{mock, Matcher};
use url::Url; use url::Url;
@ -441,4 +443,32 @@ mod test {
assert!(room.deref().power_levels.is_some()) assert!(room.deref().power_levels.is_some())
} }
#[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_!(admin.power_level.unwrap(), js_int::Int::new(100).unwrap());
Ok(())
}
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)
.build_room_runner(&rid, &uid)
.add_room_assert(test_room_power)
.add_room_assert(test_room_users);
runner.run_test().await;
}
} }

View File

@ -184,92 +184,66 @@ impl RoomMember {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::identifiers::{EventId, RoomId, UserId}; use crate::events::room::member::MembershipState;
use crate::{AsyncClient, Session, SyncSettings}; use crate::events::collections::all::RoomEvent;
use crate::events::presence::PresenceState;
use serde_json; use crate::identifiers::{RoomId, UserId};
use crate::test_builder::{EventBuilder};
use crate::{assert_, assert_eq_, Room};
use js_int::{Int, UInt}; use js_int::{Int, UInt};
use mockito::{mock, Matcher};
use url::Url;
use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::ops::Deref;
use std::str::FromStr;
use std::time::Duration;
use crate::events::room::power_levels::{
NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent,
};
#[tokio::test] #[tokio::test]
async fn member_power() { async fn room_member_events() {
let homeserver = Url::from_str(&mockito::server_url()).unwrap(); fn test_room_member(room: &Room) -> Result<(), String> {
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(())
}
let session = Session { let rid = RoomId::try_from("!roomid:room.com").unwrap();
access_token: "1234".to_owned(), let uid = UserId::try_from("@example:localhost").unwrap();
user_id: UserId::try_from("@example:localhost").unwrap(), let bld = EventBuilder::default();
device_id: "DEVICEID".to_owned(), 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);
let _m = mock( runner.run_test().await;
"GET",
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
)
.with_status(200)
.with_body_from_file("tests/data/sync.json")
.create();
let mut client = AsyncClient::new(homeserver, Some(session)).unwrap();
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
let _response = client.sync(sync_settings).await.unwrap();
let mut rooms = client.base_client.write().await.joined_rooms.clone();
let mut room = rooms
.get_mut(&RoomId::try_from("!SVkFJHzfwvuaIEawgC:localhost").unwrap())
.unwrap()
.lock()
.await;
let power = power_levels();
assert!(room.handle_power_level(&power));
assert_eq!(
room.deref().power_levels.as_ref().unwrap().ban,
Int::new(40).unwrap()
);
assert_eq!(
room.deref().power_levels.as_ref().unwrap().notifications,
Int::new(35).unwrap()
);
} }
fn power_levels() -> PowerLevelsEvent { #[tokio::test]
PowerLevelsEvent { async fn member_presence_events() {
content: PowerLevelsEventContent { fn test_room_member(room: &Room) -> Result<(), String> {
ban: Int::new(40).unwrap(), let member = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap();
events: HashMap::default(), assert_eq_!(member.membership, MembershipState::Join);
events_default: Int::new(40).unwrap(), assert_eq_!(member.power_level, Int::new(100));
invite: Int::new(40).unwrap(), println!("{:#?}", room);
kick: Int::new(40).unwrap(), Ok(())
redact: Int::new(40).unwrap(),
state_default: Int::new(40).unwrap(),
users: HashMap::default(),
users_default: Int::new(40).unwrap(),
notifications: NotificationPowerLevels {
room: Int::new(35).unwrap(),
},
},
event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(),
origin_server_ts: UInt::new(1520372800469).unwrap(),
prev_content: None,
room_id: RoomId::try_from("!roomid:room.com").ok(),
unsigned: serde_json::Map::new(),
sender: UserId::try_from("@example:example.com").unwrap(),
state_key: "@example:example.com".into(),
} }
fn test_presence(room: &Room) -> Result<(), String> {
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));
Ok(())
}
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)
.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;
} }
} }

View File

@ -1,210 +1,430 @@
#![cfg(test)]
use std::fs;
use std::path::Path;
use std::panic;
use crate::identifiers::{RoomId, UserId};
use crate::events::{
collections::all::{Event, RoomEvent, StateEvent},
presence::PresenceEvent,
EventResult, TryFromRaw,
};
use crate::{AsyncClient};
#[cfg(test)] use ansi_term::Colour;
mod test_it { use mockito::mock;
#![allow(unused)]
use std::fs; use crate::models::Room;
use std::path::Path;
use std::panic;
use std::convert::TryFrom;
use std::str::FromStr;
use std::time::Duration;
use crate::identifiers::{RoomId, UserId}; /// `assert` to use in `TestRunner`.
use crate::events::{ ///
collections::all::RoomEvent, /// This returns an `Err` on failure, instead of panicking.
room::{ #[macro_export]
power_levels::PowerLevelsEvent, macro_rules! assert_ {
}, ($truth:expr) => {
EventResult, FromRaw, TryFromRaw, if !$truth {
return Err(format!(r#"assertion failed: `(left == right)`
expression: `{:?}`
failed at {}"#,
stringify!($truth),
file!(),
))
}
}; };
use crate::{AsyncClient, Session, SyncSettings}; }
use ansi_term::Colour; /// `assert_eq` to use in `TestRunner.
use serde_json::Value; ///
use mockito::{mock, Matcher}; /// This returns an `Err` on failure, instead of panicking.
use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[macro_export]
use url::Url; macro_rules! assert_eq_ {
($left:expr, $right:expr) => ({
use crate::models::Room; match (&$left, &$right) {
(left_val, right_val) => {
#[derive(Default)] if !(*left_val == *right_val) {
pub struct ResponseBuilder { return Err(format!(r#"assertion failed: `(left == right)`
events: Vec<RoomEvent>, left: `{:?}`,
} right: `{:?}`
failed at {}:{}"#,
pub struct TestRunner { &*left_val,
/// Used when testing the whole client &*right_val,
client: Option<AsyncClient>, file!(),
/// Used To test the models line!()
room: Option<Room>, ))
/// When testing the models a vec of RoomEvents is needed. }
events: Vec<RoomEvent>, }
/// A `Vec` of callbacks that should assert something about the client. }
/// });
/// The callback should panic if the state is unexpected (use `assert_*!` macro) ($left:expr, $right:expr,) => ({
client_assertions: Vec<fn(&AsyncClient)>, $crate::assert_eq!($left, $right)
/// A `Vec` of callbacks that should assert something about the room. });
/// ($left:expr, $right:expr, $($arg:tt)+) => ({
/// The callback should panic if the state is unexpected (use `assert_*!` macro) match (&($left), &($right)) {
room_assertions: Vec<fn(&Room)>, (left_val, right_val) => {
/// `mokito::Mock` if !(*left_val == *right_val) {
mock: Option<mockito::Mock>, return Err(format!(r#"assertion failed: `(left == right)`
} left: `{:?}`,
right: `{:?}` : {}
impl ResponseBuilder { failed at {}:{}"#,
&*left_val,
/// Creates an `IncomingResponse` to hold events for a sync. &*right_val,
pub fn create_sync_response(mut self) -> Self { $crate::format_args!($($arg)+),
file!(),
self line!(),
} ))
}
/// Just throw events at the client, not part of a specific response. }
pub fn create_event_stream(mut self) -> Self { }
});
self }
}
/// `assert_ne` to use in `TestRunner.
/// Add an event to the events `Vec`. ///
pub fn add_event_from_file<Ev: TryFromRaw, P: AsRef<Path>>(mut self, path: P, variant: fn(Ev) -> RoomEvent) -> Self { /// This returns an `Err` on failure, instead of panicking.
let val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref())); #[macro_export]
let event = serde_json::from_str::<ruma_events::EventResult<Ev>>(&val).unwrap().into_result().unwrap(); macro_rules! assert_ne_ {
self.add_event(variant(event)); ($left:expr, $right:expr) => ({
self match (&$left, &$right) {
} (left_val, right_val) => {
if (*left_val == *right_val) {
fn add_event(&mut self, event: RoomEvent) { return Err(format!(r#"assertion failed: `(left == right)`
self.events.push(event) left: `{:?}`,
} right: `{:?}`
failed at {}:{}"#,
/// Consumes `ResponseBuilder and returns a `TestRunner`. &*left_val,
/// &*right_val,
/// The `TestRunner` streams the events to the client and enables methods to set assertions file!(),
/// about the state of the client. line!()
pub fn build_responses(mut self, method: &str, path: &str) -> TestRunner { ))
let body = serde_json::to_string(&self.events).unwrap(); }
let mock = Some(mock(method, path) }
.with_status(200) }
.with_body(body) });
.create()); ($left:expr, $right:expr,) => ({
$crate::assert_eq!($left, $right)
TestRunner { });
client: None, ($left:expr, $right:expr, $($arg:tt)+) => ({
room: None, match (&($left), &($right)) {
events: Vec::new(), (left_val, right_val) => {
client_assertions: Vec::new(), if (*left_val == *right_val) {
room_assertions: Vec::new(), return Err(format!(r#"assertion failed: `(left == right)`
mock, left: `{:?}`,
} right: `{:?}` : {}
} failed at {}:{}"#,
&*left_val,
/// Consumes `ResponseBuilder and returns a `TestRunner`. &*right_val,
/// $crate::format_args!($($arg)+),
/// The `TestRunner` streams the events to the client and enables methods to set assertions file!(),
/// about the state of the client. line!(),
pub fn build_room_events(mut self, room_id: &RoomId, user_id: &UserId) -> TestRunner { ))
TestRunner { }
client: None, }
room: Some(Room::new(room_id, user_id)), }
events: self.events, });
client_assertions: Vec::new(), }
room_assertions: Vec::new(),
mock: None, /// 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
impl TestRunner { /// ```rust
pub fn set_client(mut self, client: AsyncClient) -> Self { /// # use matrix_sdk::AsyncClient;
self.client = Some(client); /// # use url::Url;
self /// async_assert!{
} /// async fn foo(cli: &AsyncClient) -> Result<(), String> {
/// assert_eq_!(cli.homeserver(), Url::new("matrix.org"))
pub fn add_client_assert(mut self, assert: fn(&AsyncClient)) -> Self { /// Ok(())
self.client_assertions.push(assert); /// }
self /// }
} /// ```
#[macro_export]
pub fn add_room_assert(mut self, assert: fn(&Room)) -> Self { macro_rules! async_assert {
self.room_assertions.push(assert); (
self $( #[$attr:meta] )*
} $pub:vis async fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? {
$($body:tt)*
fn run_client_tests(mut self) -> Result<(), Vec<String>> { }
Ok(()) ) => (
} $( #[$attr] )*
#[allow(unused_parens)]
fn run_room_tests(mut self) -> Result<(), Vec<String>> { $pub fn $fname<$lt> ( $($args)* )
let mut errs = Vec::new(); -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ($($Ret)?)>
let mut room = self.room.unwrap(); + ::std::marker::Send + $lt>>
for event in &self.events { {
match event { ::std::boxed::Box::pin(async move { $($body)* })
RoomEvent::RoomMember(m) => room.handle_membership(m), }
RoomEvent::RoomName(n) => room.handle_room_name(n), );
RoomEvent::RoomCanonicalAlias(ca) => room.handle_canonical(ca), (
RoomEvent::RoomAliases(a) => room.handle_room_aliases(a), $( #[$attr:meta] )*
RoomEvent::RoomPowerLevels(p) => room.handle_power_level(p), $pub:vis async fn $fname:ident ( $($args:tt)* ) $(-> $Ret:ty)? {
// RoomEvent::RoomEncryption(e) => room.handle_encryption_event(e), $($body:tt)*
_ => todo!("implement more RoomEvent variants"), }
}; ) => (
} $( #[$attr] )*
for assert in self.room_assertions { #[allow(unused_parens)]
if let Err(e) = panic::catch_unwind(|| assert(&room)) { $pub fn $fname ( $($args)* )
errs.push(stringify!(e).to_string()); -> ::std::pin::Pin<::std::boxed::Box<(dyn ::std::future::Future<Output = ($($Ret)?)>
} + ::std::marker::Send)>>
} {
if errs.is_empty() { ::std::boxed::Box::pin(async move { $($body)* })
Ok(()) }
} else { )
Err(errs) }
}
} type DynFuture<'lt, T> = ::std::pin::Pin<Box<dyn 'lt + Send + ::std::future::Future<Output = T>>>;
pub type AsyncAssert = fn(&AsyncClient) -> DynFuture<Result<(), String>>;
pub fn run_test(mut self) {
let errs = if let Some(room) = &self.room { #[derive(Default)]
self.run_room_tests() pub struct EventBuilder {
} else if let Some(cli) = &self.client { /// The events that determine the state of a `Room`.
self.run_client_tests() ///
} else { /// When testing the models `RoomEvent`s are needed.
panic!("must have either AsyncClient or Room") room_events: Vec<RoomEvent>,
}; /// The presence events that determine the presence state of a `RoomMember`.
presence_events: Vec<PresenceEvent>,
if let Err(errs) = errs { /// The state events that determine the state of a `Room`.
let err_str = errs.join(&format!("{}\n", Colour::Red.paint("Error: "))); state_events: Vec<StateEvent>,
if !errs.is_empty() { /// The state events that determine the state of a `Room`.
panic!("{}", Colour::Red.paint("some tests failed")); non_room_events: Vec<Event>,
} }
}
} #[allow(dead_code)]
} pub struct TestRunner {
/// Used when testing the whole client
fn test_room_users(room: &Room) { client: Option<AsyncClient>,
assert_eq!(room.members.len(), 1); /// Used To test the models
} room: Option<Room>,
/// The non room events that determine the state of a `Room`.
fn test_room_power(room: &Room) { ///
assert!(room.power_levels.is_some()); /// These are ephemeral and account events.
assert_eq!(room.power_levels.as_ref().unwrap().kick, js_int::Int::new(50).unwrap()); non_room_events: Vec<Event>,
let admin = room.members.get(&UserId::try_from("@example:localhost").unwrap()).unwrap(); /// The events that determine the state of a `Room`.
assert_eq!(admin.power_level.unwrap(), js_int::Int::new(100).unwrap()); ///
println!("{:#?}", room); /// When testing the models `RoomEvent`s are needed.
} room_events: Vec<RoomEvent>,
/// The presence events that determine the presence state of a `RoomMember`.
#[test] presence_events: Vec<PresenceEvent>,
fn room_events() { /// The state events that determine the state of a `Room`.
let rid = RoomId::try_from("!roomid:room.com").unwrap(); state_events: Vec<StateEvent>,
let uid = UserId::try_from("@example:localhost").unwrap(); /// A `Vec` of callbacks that should assert something about the client.
let mut bld = ResponseBuilder::default(); ///
let runner = bld.add_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember) /// The callback should use the provided `assert_`, `assert_*_` macros.
.add_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) client_assertions: Vec<AsyncAssert>,
.build_room_events(&rid, &uid) /// A `Vec` of callbacks that should assert something about the room.
.add_room_assert(test_room_power) ///
.add_room_assert(test_room_users); /// The callback should use the provided `assert_`, `assert_*_` macros.
room_assertions: Vec<fn(&Room) -> Result<(), String>>,
runner.run_test(); /// `mokito::Mock`
} mock: Option<mockito::Mock>,
}
#[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<Ev: TryFromRaw, P: AsRef<Path>>(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::<EventResult<Ev>>(&val).unwrap().into_result().unwrap();
self.non_room_events.push(variant(event));
self
}
/// Add an event to the room events `Vec`.
pub fn add_room_event_from_file<Ev: TryFromRaw, P: AsRef<Path>>(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::<EventResult<Ev>>(&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<Ev: TryFromRaw, P: AsRef<Path>>(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::<EventResult<Ev>>(&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<P: AsRef<Path>>(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::<EventResult<PresenceEvent>>(&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 {
client: None,
room: None,
non_room_events: 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 {
client: None,
room: Some(Room::new(room_id, user_id)),
non_room_events: self.non_room_events,
room_events: self.room_events,
presence_events: self.presence_events,
state_events: self.state_events,
client_assertions: Vec::new(),
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
}
/// 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<String>> {
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<String>> {
let mut errs = Vec::new();
let room = self.room.as_mut().unwrap();
for event in &self.non_room_events {
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"),
};
}
for event in &self.room_events {
room.receive_timeline_event(event);
}
for event in &self.presence_events {
room.receive_presence_event(event);
}
for event in &self.state_events {
room.receive_state_event(event);
}
for assert in &mut self.room_assertions {
if let Err(e) = assert(&room) {
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.room {
(self.room_assertions.len(), self.run_room_tests())
} else if let Some(_) = &self.client {
(self.client_assertions.len(), self.run_client_tests().await)
} else {
panic!("must have either AsyncClient or Room")
};
if let Err(errs) = errs {
let err_str = errs.join(&format!("\n\n"));
println!("{}\n{}", Colour::Red.paint("Error: "), err_str);
if !errs.is_empty() {
panic!("{} tests failed", errs.len());
} else {
eprintln!("{}. {} passed", Colour::Green.paint("Ok"), count);
}
}
}
} }

View File

@ -0,0 +1,11 @@
{
"content": {
"avatar_url": "mxc://localhost:wefuiwegh8742w",
"currently_active": false,
"last_active_ago": 1,
"presence": "online",
"status_msg": "Making cupcakes"
},
"sender": "@example:localhost",
"type": "m.presence"
}