more work needed for AsyncClient test runner
parent
78f92130fa
commit
9ffe7cca10
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
let _m = mock(
|
.add_room_assert(test_room_member);
|
||||||
"GET",
|
|
||||||
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
runner.run_test().await;
|
||||||
)
|
|
||||||
.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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
/// `assert` to use in `TestRunner`.
|
||||||
use std::convert::TryFrom;
|
///
|
||||||
use std::str::FromStr;
|
/// This returns an `Err` on failure, instead of panicking.
|
||||||
use std::time::Duration;
|
#[macro_export]
|
||||||
|
macro_rules! assert_ {
|
||||||
use crate::identifiers::{RoomId, UserId};
|
($truth:expr) => {
|
||||||
use crate::events::{
|
if !$truth {
|
||||||
collections::all::RoomEvent,
|
return Err(format!(r#"assertion failed: `(left == right)`
|
||||||
room::{
|
expression: `{:?}`
|
||||||
power_levels::PowerLevelsEvent,
|
failed at {}"#,
|
||||||
},
|
stringify!($truth),
|
||||||
EventResult, FromRaw, TryFromRaw,
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
Loading…
Reference in New Issue