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();
let session = Session { assert_eq_!(member.membership, MembershipState::Join);
access_token: "1234".to_owned(), assert_eq_!(member.power_level, Int::new(100));
user_id: UserId::try_from("@example:localhost").unwrap(), println!("{:#?}", room);
device_id: "DEVICEID".to_owned(), Ok(())
};
let _m = mock(
"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 { let rid = RoomId::try_from("!roomid:room.com").unwrap();
PowerLevelsEvent { let uid = UserId::try_from("@example:localhost").unwrap();
content: PowerLevelsEventContent { let bld = EventBuilder::default();
ban: Int::new(40).unwrap(), let runner = bld.add_room_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember)
events: HashMap::default(), .add_room_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels)
events_default: Int::new(40).unwrap(), .build_room_runner(&rid, &uid)
invite: Int::new(40).unwrap(), .add_room_assert(test_room_member);
kick: Int::new(40).unwrap(),
redact: Int::new(40).unwrap(), runner.run_test().await;
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(),
} }
#[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();
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();
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,60 +1,228 @@
#![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)`
use crate::{AsyncClient, Session, SyncSettings}; expression: `{:?}`
failed at {}"#,
use ansi_term::Colour; stringify!($truth),
use serde_json::Value; file!(),
use mockito::{mock, Matcher}; ))
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use url::Url;
use crate::models::Room;
#[derive(Default)]
pub struct ResponseBuilder {
events: Vec<RoomEvent>,
} }
};
}
pub struct TestRunner { /// `assert_eq` to use in `TestRunner.
///
/// This returns an `Err` on failure, instead of panicking.
#[macro_export]
macro_rules! assert_eq_ {
($left:expr, $right:expr) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
return Err(format!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`
failed at {}:{}"#,
&*left_val,
&*right_val,
file!(),
line!()
))
}
}
}
});
($left:expr, $right:expr,) => ({
$crate::assert_eq!($left, $right)
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
return Err(format!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}` : {}
failed at {}:{}"#,
&*left_val,
&*right_val,
$crate::format_args!($($arg)+),
file!(),
line!(),
))
}
}
}
});
}
/// `assert_ne` to use in `TestRunner.
///
/// This returns an `Err` on failure, instead of panicking.
#[macro_export]
macro_rules! assert_ne_ {
($left:expr, $right:expr) => ({
match (&$left, &$right) {
(left_val, right_val) => {
if (*left_val == *right_val) {
return Err(format!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}`
failed at {}:{}"#,
&*left_val,
&*right_val,
file!(),
line!()
))
}
}
}
});
($left:expr, $right:expr,) => ({
$crate::assert_eq!($left, $right)
});
($left:expr, $right:expr, $($arg:tt)+) => ({
match (&($left), &($right)) {
(left_val, right_val) => {
if (*left_val == *right_val) {
return Err(format!(r#"assertion failed: `(left == right)`
left: `{:?}`,
right: `{:?}` : {}
failed at {}:{}"#,
&*left_val,
&*right_val,
$crate::format_args!($($arg)+),
file!(),
line!(),
))
}
}
}
});
}
/// 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!{
/// async fn foo(cli: &AsyncClient) -> Result<(), String> {
/// assert_eq_!(cli.homeserver(), Url::new("matrix.org"))
/// Ok(())
/// }
/// }
/// ```
#[macro_export]
macro_rules! async_assert {
(
$( #[$attr:meta] )*
$pub:vis async fn $fname:ident<$lt:lifetime> ( $($args:tt)* ) $(-> $Ret:ty)? {
$($body:tt)*
}
) => (
$( #[$attr] )*
#[allow(unused_parens)]
$pub fn $fname<$lt> ( $($args)* )
-> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ($($Ret)?)>
+ ::std::marker::Send + $lt>>
{
::std::boxed::Box::pin(async move { $($body)* })
}
);
(
$( #[$attr:meta] )*
$pub:vis async fn $fname:ident ( $($args:tt)* ) $(-> $Ret:ty)? {
$($body:tt)*
}
) => (
$( #[$attr] )*
#[allow(unused_parens)]
$pub fn $fname ( $($args)* )
-> ::std::pin::Pin<::std::boxed::Box<(dyn ::std::future::Future<Output = ($($Ret)?)>
+ ::std::marker::Send)>>
{
::std::boxed::Box::pin(async move { $($body)* })
}
)
}
type DynFuture<'lt, T> = ::std::pin::Pin<Box<dyn 'lt + Send + ::std::future::Future<Output = T>>>;
pub type AsyncAssert = fn(&AsyncClient) -> DynFuture<Result<(), String>>;
#[derive(Default)]
pub struct EventBuilder {
/// The events that determine the state of a `Room`.
///
/// When testing the models `RoomEvent`s are needed.
room_events: Vec<RoomEvent>,
/// The presence events that determine the presence state of a `RoomMember`.
presence_events: Vec<PresenceEvent>,
/// The state events that determine the state of a `Room`.
state_events: Vec<StateEvent>,
/// The state events that determine the state of a `Room`.
non_room_events: Vec<Event>,
}
#[allow(dead_code)]
pub struct TestRunner {
/// Used when testing the whole client /// Used when testing the whole client
client: Option<AsyncClient>, client: Option<AsyncClient>,
/// Used To test the models /// Used To test the models
room: Option<Room>, room: Option<Room>,
/// When testing the models a vec of RoomEvents is needed. /// The non room events that determine the state of a `Room`.
events: Vec<RoomEvent>, ///
/// These are ephemeral and account events.
non_room_events: Vec<Event>,
/// The events that determine the state of a `Room`.
///
/// When testing the models `RoomEvent`s are needed.
room_events: Vec<RoomEvent>,
/// The presence events that determine the presence state of a `RoomMember`.
presence_events: Vec<PresenceEvent>,
/// The state events that determine the state of a `Room`.
state_events: Vec<StateEvent>,
/// A `Vec` of callbacks that should assert something about the client. /// A `Vec` of callbacks that should assert something about the client.
/// ///
/// The callback should panic if the state is unexpected (use `assert_*!` macro) /// The callback should use the provided `assert_`, `assert_*_` macros.
client_assertions: Vec<fn(&AsyncClient)>, client_assertions: Vec<AsyncAssert>,
/// A `Vec` of callbacks that should assert something about the room. /// A `Vec` of callbacks that should assert something about the room.
/// ///
/// The callback should panic if the state is unexpected (use `assert_*!` macro) /// The callback should use the provided `assert_`, `assert_*_` macros.
room_assertions: Vec<fn(&Room)>, room_assertions: Vec<fn(&Room) -> Result<(), String>>,
/// `mokito::Mock` /// `mokito::Mock`
mock: Option<mockito::Mock>, mock: Option<mockito::Mock>,
} }
impl ResponseBuilder { #[allow(dead_code)]
#[allow(unused_mut)]
impl EventBuilder {
/// Creates an `IncomingResponse` to hold events for a sync. /// Creates an `IncomingResponse` to hold events for a sync.
pub fn create_sync_response(mut self) -> Self { pub fn create_sync_response(mut self) -> Self {
@ -68,24 +236,45 @@ mod test_it {
self self
} }
/// Add an event to the events `Vec`. /// Add an event to the room events `Vec`.
pub fn add_event_from_file<Ev: TryFromRaw, P: AsRef<Path>>(mut self, path: P, variant: fn(Ev) -> RoomEvent) -> Self { 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 val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref()));
let event = serde_json::from_str::<ruma_events::EventResult<Ev>>(&val).unwrap().into_result().unwrap(); let event = serde_json::from_str::<EventResult<Ev>>(&val).unwrap().into_result().unwrap();
self.add_event(variant(event)); self.non_room_events.push(variant(event));
self self
} }
fn add_event(&mut self, event: RoomEvent) { /// Add an event to the room events `Vec`.
self.events.push(event) 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`. /// Consumes `ResponseBuilder and returns a `TestRunner`.
/// ///
/// The `TestRunner` streams the events to the client and enables methods to set assertions /// The `TestRunner` streams the events to the client and holds methods to make assertions
/// about the state of the client. /// about the state of the client.
pub fn build_responses(mut self, method: &str, path: &str) -> TestRunner { pub fn build_client_runner(mut self, method: &str, path: &str) -> TestRunner {
let body = serde_json::to_string(&self.events).unwrap(); // TODO serialize this properly
let body = serde_json::to_string(&self.room_events).unwrap();
let mock = Some(mock(method, path) let mock = Some(mock(method, path)
.with_status(200) .with_status(200)
.with_body(body) .with_body(body)
@ -94,7 +283,10 @@ mod test_it {
TestRunner { TestRunner {
client: None, client: None,
room: None, room: None,
events: Vec::new(), non_room_events: Vec::new(),
room_events: Vec::new(),
presence_events: Vec::new(),
state_events: Vec::new(),
client_assertions: Vec::new(), client_assertions: Vec::new(),
room_assertions: Vec::new(), room_assertions: Vec::new(),
mock, mock,
@ -103,57 +295,74 @@ mod test_it {
/// Consumes `ResponseBuilder and returns a `TestRunner`. /// Consumes `ResponseBuilder and returns a `TestRunner`.
/// ///
/// The `TestRunner` streams the events to the client and enables methods to set assertions /// The `TestRunner` streams the events to the `Room` and holds methods to make assertions
/// about the state of the client. /// about the state of the `Room`.
pub fn build_room_events(mut self, room_id: &RoomId, user_id: &UserId) -> TestRunner { pub fn build_room_runner(self, room_id: &RoomId, user_id: &UserId) -> TestRunner {
TestRunner { TestRunner {
client: None, client: None,
room: Some(Room::new(room_id, user_id)), room: Some(Room::new(room_id, user_id)),
events: self.events, 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(), client_assertions: Vec::new(),
room_assertions: Vec::new(), room_assertions: Vec::new(),
mock: None, mock: None,
} }
} }
} }
impl TestRunner { #[allow(dead_code)]
impl TestRunner {
pub fn set_client(mut self, client: AsyncClient) -> Self { pub fn set_client(mut self, client: AsyncClient) -> Self {
self.client = Some(client); self.client = Some(client);
self self
} }
pub fn add_client_assert(mut self, assert: fn(&AsyncClient)) -> 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.client_assertions.push(assert);
self self
} }
pub fn add_room_assert(mut self, assert: fn(&Room)) -> Self { pub fn add_room_assert(mut self, assert: fn(&Room) -> Result<(), String>) -> Self {
self.room_assertions.push(assert); self.room_assertions.push(assert);
self self
} }
fn run_client_tests(mut self) -> Result<(), Vec<String>> { async fn run_client_tests(&mut self) -> Result<(), Vec<String>> {
Ok(())
}
fn run_room_tests(mut self) -> Result<(), Vec<String>> {
let mut errs = Vec::new(); let mut errs = Vec::new();
let mut room = self.room.unwrap(); let mut cli = self.client.as_ref().unwrap().base_client.write().await;
for event in &self.events { let room_id = &self.room.as_ref().unwrap().room_id;
for event in &self.non_room_events {
match event { match event {
RoomEvent::RoomMember(m) => room.handle_membership(m), // Event::IgnoredUserList(iu) => room.handle_ignored_users(iu),
RoomEvent::RoomName(n) => room.handle_room_name(n), Event::Presence(p) => cli.receive_presence_event(room_id, p).await,
RoomEvent::RoomCanonicalAlias(ca) => room.handle_canonical(ca), // Event::PushRules(pr) => room.handle_push_rules(pr),
RoomEvent::RoomAliases(a) => room.handle_room_aliases(a), // TODO receive ephemeral events
RoomEvent::RoomPowerLevels(p) => room.handle_power_level(p), _ => todo!("implement more non room events"),
// RoomEvent::RoomEncryption(e) => room.handle_encryption_event(e),
_ => todo!("implement more RoomEvent variants"),
}; };
} }
for assert in self.room_assertions {
if let Err(e) = panic::catch_unwind(|| assert(&room)) { for event in &self.room_events {
errs.push(stringify!(e).to_string()); 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() { if errs.is_empty() {
@ -163,48 +372,59 @@ mod test_it {
} }
} }
pub fn run_test(mut self) { fn run_room_tests(&mut self) -> Result<(), Vec<String>> {
let errs = if let Some(room) = &self.room { let mut errs = Vec::new();
self.run_room_tests() let room = self.room.as_mut().unwrap();
} else if let Some(cli) = &self.client {
self.run_client_tests() 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 { } else {
panic!("must have either AsyncClient or Room") panic!("must have either AsyncClient or Room")
}; };
if let Err(errs) = errs { if let Err(errs) = errs {
let err_str = errs.join(&format!("{}\n", Colour::Red.paint("Error: "))); let err_str = errs.join(&format!("\n\n"));
println!("{}\n{}", Colour::Red.paint("Error: "), err_str);
if !errs.is_empty() { if !errs.is_empty() {
panic!("{}", Colour::Red.paint("some tests failed")); panic!("{} tests failed", errs.len());
} else {
eprintln!("{}. {} passed", Colour::Green.paint("Ok"), count);
} }
} }
} }
}
fn test_room_users(room: &Room) {
assert_eq!(room.members.len(), 1);
}
fn test_room_power(room: &Room) {
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());
println!("{:#?}", room);
}
#[test]
fn room_events() {
let rid = RoomId::try_from("!roomid:room.com").unwrap();
let uid = UserId::try_from("@example:localhost").unwrap();
let mut bld = ResponseBuilder::default();
let runner = bld.add_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember)
.add_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels)
.build_room_events(&rid, &uid)
.add_room_assert(test_room_power)
.add_room_assert(test_room_users);
runner.run_test();
}
} }

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"
}