more work needed for AsyncClient test runner
parent
78f92130fa
commit
9ffe7cca10
|
@ -790,3 +790,42 @@ impl AsyncClient {
|
|||
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
|
||||
//! destroyed.
|
||||
#![deny(missing_docs)]
|
||||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
pub use crate::{error::Error, error::Result, session::Session};
|
||||
pub use reqwest::header::InvalidHeaderValue;
|
||||
|
@ -40,8 +41,8 @@ mod event_emitter;
|
|||
mod models;
|
||||
mod session;
|
||||
|
||||
// TODO remove
|
||||
mod test_builder;
|
||||
#[cfg(test)]
|
||||
pub mod test_builder;
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
mod crypto;
|
||||
|
|
|
@ -389,6 +389,8 @@ mod test {
|
|||
use crate::events::room::member::MembershipState;
|
||||
use crate::identifiers::UserId;
|
||||
use crate::{AsyncClient, Session, SyncSettings};
|
||||
use crate::test_builder::{EventBuilder};
|
||||
use crate::{assert_, assert_eq_};
|
||||
|
||||
use mockito::{mock, Matcher};
|
||||
use url::Url;
|
||||
|
@ -441,4 +443,32 @@ mod test {
|
|||
|
||||
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)]
|
||||
mod test {
|
||||
use crate::identifiers::{EventId, RoomId, UserId};
|
||||
use crate::{AsyncClient, Session, SyncSettings};
|
||||
|
||||
use serde_json;
|
||||
use crate::events::room::member::MembershipState;
|
||||
use crate::events::collections::all::RoomEvent;
|
||||
use crate::events::presence::PresenceState;
|
||||
use crate::identifiers::{RoomId, UserId};
|
||||
use crate::test_builder::{EventBuilder};
|
||||
use crate::{assert_, assert_eq_, Room};
|
||||
|
||||
use js_int::{Int, UInt};
|
||||
use mockito::{mock, Matcher};
|
||||
use url::Url;
|
||||
|
||||
use std::collections::HashMap;
|
||||
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]
|
||||
async fn member_power() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
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();
|
||||
assert_eq_!(member.membership, MembershipState::Join);
|
||||
assert_eq_!(member.power_level, Int::new(100));
|
||||
println!("{:#?}", room);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let session = Session {
|
||||
access_token: "1234".to_owned(),
|
||||
user_id: UserId::try_from("@example:localhost").unwrap(),
|
||||
device_id: "DEVICEID".to_owned(),
|
||||
};
|
||||
|
||||
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()
|
||||
);
|
||||
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_member);
|
||||
|
||||
runner.run_test().await;
|
||||
}
|
||||
|
||||
fn power_levels() -> PowerLevelsEvent {
|
||||
PowerLevelsEvent {
|
||||
content: PowerLevelsEventContent {
|
||||
ban: Int::new(40).unwrap(),
|
||||
events: HashMap::default(),
|
||||
events_default: Int::new(40).unwrap(),
|
||||
invite: Int::new(40).unwrap(),
|
||||
kick: Int::new(40).unwrap(),
|
||||
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(),
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
mod test_it {
|
||||
#![allow(unused)]
|
||||
use ansi_term::Colour;
|
||||
use mockito::mock;
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::panic;
|
||||
use std::convert::TryFrom;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::identifiers::{RoomId, UserId};
|
||||
use crate::events::{
|
||||
collections::all::RoomEvent,
|
||||
room::{
|
||||
power_levels::PowerLevelsEvent,
|
||||
},
|
||||
EventResult, FromRaw, TryFromRaw,
|
||||
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)`
|
||||
expression: `{:?}`
|
||||
failed at {}"#,
|
||||
stringify!($truth),
|
||||
file!(),
|
||||
))
|
||||
}
|
||||
};
|
||||
use crate::{AsyncClient, Session, SyncSettings};
|
||||
|
||||
use ansi_term::Colour;
|
||||
use serde_json::Value;
|
||||
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 {
|
||||
/// Used when testing the whole client
|
||||
client: Option<AsyncClient>,
|
||||
/// Used To test the models
|
||||
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)
|
||||
client_assertions: Vec<fn(&AsyncClient)>,
|
||||
/// A `Vec` of callbacks that should assert something about the room.
|
||||
///
|
||||
/// The callback should panic if the state is unexpected (use `assert_*!` macro)
|
||||
room_assertions: Vec<fn(&Room)>,
|
||||
/// `mokito::Mock`
|
||||
mock: Option<mockito::Mock>,
|
||||
}
|
||||
|
||||
impl ResponseBuilder {
|
||||
|
||||
/// 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 events `Vec`.
|
||||
pub fn add_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::<ruma_events::EventResult<Ev>>(&val).unwrap().into_result().unwrap();
|
||||
self.add_event(variant(event));
|
||||
self
|
||||
}
|
||||
|
||||
fn add_event(&mut self, event: RoomEvent) {
|
||||
self.events.push(event)
|
||||
}
|
||||
|
||||
/// Consumes `ResponseBuilder and returns a `TestRunner`.
|
||||
///
|
||||
/// The `TestRunner` streams the events to the client and enables methods to set assertions
|
||||
/// about the state of the client.
|
||||
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());
|
||||
|
||||
TestRunner {
|
||||
client: None,
|
||||
room: None,
|
||||
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 client and enables methods to set assertions
|
||||
/// about the state of the client.
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TestRunner {
|
||||
pub fn set_client(mut self, client: AsyncClient) -> Self {
|
||||
self.client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_client_assert(mut self, assert: fn(&AsyncClient)) -> Self {
|
||||
self.client_assertions.push(assert);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_room_assert(mut self, assert: fn(&Room)) -> Self {
|
||||
self.room_assertions.push(assert);
|
||||
self
|
||||
}
|
||||
|
||||
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 room = self.room.unwrap();
|
||||
for event in &self.events {
|
||||
match event {
|
||||
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),
|
||||
RoomEvent::RoomPowerLevels(p) => room.handle_power_level(p),
|
||||
// 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)) {
|
||||
errs.push(stringify!(e).to_string());
|
||||
}
|
||||
}
|
||||
if errs.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errs)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_test(mut self) {
|
||||
let errs = if let Some(room) = &self.room {
|
||||
self.run_room_tests()
|
||||
} else if let Some(cli) = &self.client {
|
||||
self.run_client_tests()
|
||||
} else {
|
||||
panic!("must have either AsyncClient or Room")
|
||||
};
|
||||
|
||||
if let Err(errs) = errs {
|
||||
let err_str = errs.join(&format!("{}\n", Colour::Red.paint("Error: ")));
|
||||
if !errs.is_empty() {
|
||||
panic!("{}", Colour::Red.paint("some tests failed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// `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
|
||||
client: Option<AsyncClient>,
|
||||
/// Used To test the models
|
||||
room: Option<Room>,
|
||||
/// The non room events that determine the state of a `Room`.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// The callback should use the provided `assert_`, `assert_*_` macros.
|
||||
client_assertions: Vec<AsyncAssert>,
|
||||
/// A `Vec` of callbacks that should assert something about the room.
|
||||
///
|
||||
/// The callback should use the provided `assert_`, `assert_*_` macros.
|
||||
room_assertions: Vec<fn(&Room) -> Result<(), String>>,
|
||||
/// `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