add individual events for tests, working TestRunner

master
Devin R 2020-04-06 15:29:38 -04:00
parent 854948fc6d
commit 78f92130fa
20 changed files with 377 additions and 23 deletions

View File

@ -65,3 +65,4 @@ tracing-subscriber = "0.2.3"
tempfile = "3.1.0" tempfile = "3.1.0"
mockito = "0.23.3" mockito = "0.23.3"
serde = "1.0.105" serde = "1.0.105"
ansi_term = "0.12.1"

View File

@ -7,8 +7,12 @@ mod test_it {
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
use std::panic;
use std::convert::TryFrom;
use std::str::FromStr;
use std::time::Duration;
use crate::identifiers::UserId; use crate::identifiers::{RoomId, UserId};
use crate::events::{ use crate::events::{
collections::all::RoomEvent, collections::all::RoomEvent,
room::{ room::{
@ -18,15 +22,13 @@ mod test_it {
}; };
use crate::{AsyncClient, Session, SyncSettings}; use crate::{AsyncClient, Session, SyncSettings};
use ansi_term::Colour;
use serde_json::Value; use serde_json::Value;
use mockito::{mock, Matcher}; use mockito::{mock, Matcher};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use url::Url; use url::Url;
use std::convert::TryFrom; use crate::models::Room;
use std::str::FromStr;
use std::time::Duration;
use std::panic;
#[derive(Default)] #[derive(Default)]
pub struct ResponseBuilder { pub struct ResponseBuilder {
@ -34,19 +36,28 @@ mod test_it {
} }
pub struct TestRunner { pub struct TestRunner {
/// Used when testing the whole client
client: Option<AsyncClient>, 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. /// 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 panic if the state is unexpected (use `assert_*!` macro)
assertions: Vec<fn(&AsyncClient)>, 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` /// `mokito::Mock`
mock: mockito::Mock, mock: Option<mockito::Mock>,
} }
impl ResponseBuilder { impl ResponseBuilder {
/// 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) -> &mut Self { pub fn create_sync_response(mut self) -> Self {
self self
} }
@ -57,11 +68,10 @@ mod test_it {
self self
} }
/// Add an event either to the event stream or the appropriate `IncomingResponse` field. /// 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 { 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 val = fs::read_to_string(path.as_ref()).expect(&format!("file not found {:?}", path.as_ref()));
let json = serde_json::value::Value::from_str(&val).expect(&format!("not valid json {}", val)); let event = serde_json::from_str::<ruma_events::EventResult<Ev>>(&val).unwrap().into_result().unwrap();
let event = serde_json::from_value::<ruma_events::EventResult<Ev>>(json).unwrap().into_result().unwrap();
self.add_event(variant(event)); self.add_event(variant(event));
self self
} }
@ -74,38 +84,127 @@ mod test_it {
/// ///
/// The `TestRunner` streams the events to the client and enables methods to set assertions /// The `TestRunner` streams the events to the client and enables methods to set assertions
/// about the state of the client. /// about the state of the client.
pub fn build(mut self) -> TestRunner { pub fn build_responses(mut self, method: &str, path: &str) -> TestRunner {
let mock = mock("POST", "/_matrix/client/r0/login") let body = serde_json::to_string(&self.events).unwrap();
let mock = Some(mock(method, path)
.with_status(200) .with_status(200)
.with_body_from_file("tests/data/login_response.json") .with_body(body)
.create(); .create());
TestRunner { TestRunner {
client: None, client: None,
assertions: Vec::new(), room: None,
events: Vec::new(),
client_assertions: Vec::new(),
room_assertions: Vec::new(),
mock, 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 { impl TestRunner {
pub fn set_client(&mut self, client: AsyncClient) -> &mut Self { pub fn set_client(mut self, client: AsyncClient) -> Self {
self.client = Some(client); self.client = Some(client);
self self
} }
pub fn run_test(self) -> Result<(), Vec<String>> {
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(()) 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] #[test]
fn 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 mut bld = ResponseBuilder::default();
let runner = bld.add_event_from_file("./tests/data/events/power_levels.json", RoomEvent::RoomPowerLevels) let runner = bld.add_event_from_file("./tests/data/events/member.json", RoomEvent::RoomMember)
.build(); .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,13 @@
{
"content": {
"alias": "#tutorial:localhost"
},
"event_id": "$15139375513VdeRF:localhost",
"origin_server_ts": 1513937551461,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.canonical_alias",
"unsigned": {
"age": 7034220433
}
}

View File

@ -0,0 +1,15 @@
{
"content": {
"creator": "@example:localhost",
"m.federate": true,
"room_version": "1"
},
"event_id": "$151957878228ekrDs:localhost",
"origin_server_ts": 1519578782185,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.create",
"unsigned": {
"age": 1392989709
}
}

View File

@ -0,0 +1,7 @@
{
"content": {
"event_id": "$someplace:example.org"
},
"room_id": "!somewhere:example.org",
"type": "m.fully_read"
}

View File

@ -0,0 +1,13 @@
{
"content": {
"history_visibility": "world_readable"
},
"event_id": "$151957878235ricnD:localhost",
"origin_server_ts": 1519578782195,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.history_visibility",
"unsigned": {
"age": 1392989715
}
}

View File

@ -0,0 +1,13 @@
{
"content": {
"join_rule": "public"
},
"event_id": "$151957878231iejdB:localhost",
"origin_server_ts": 1519578782192,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.join_rules",
"unsigned": {
"age": 1392989713
}
}

View File

@ -0,0 +1,17 @@
{
"content": {
"avatar_url": null,
"displayname": "example",
"membership": "join"
},
"event_id": "$151800140517rfvjc:localhost",
"membership": "join",
"origin_server_ts": 1518001405556,
"sender": "@example:localhost",
"state_key": "@example:localhost",
"type": "m.room.member",
"unsigned": {
"age": 2970366338,
"replaces_state": "$151800111315tsynI:localhost"
}
}

View File

@ -0,0 +1,14 @@
{
"content": {
"body": "is dancing", "format": "org.matrix.custom.html",
"formatted_body": "<strong>is dancing</strong>",
"msgtype": "m.emote"
},
"event_id": "$152037280074GZeOm:localhost",
"origin_server_ts": 1520372800469,
"sender": "@example:localhost",
"type": "m.room.message",
"unsigned": {
"age": 598971425
}
}

View File

@ -0,0 +1,16 @@
{
"origin_server_ts": 1533565163841,
"sender": "@_neb_github:matrix.org",
"event_id": "$153356516319138IHRIC:matrix.org",
"unsigned": {
"age": 743
},
"content": {
"body": "https://github.com/matrix-org/matrix-python-sdk/issues/266 : Consider allowing MatrixClient.__init__ to take sync_token kwarg",
"format": "org.matrix.custom.html",
"formatted_body": "<a href='https://github.com/matrix-org/matrix-python-sdk/pull/313'>313: nio wins!</a>",
"msgtype": "m.notice"
},
"type": "m.room.message",
"room_id": "!YHhmBTmGBHGQOlGpaZ:matrix.org"
}

View File

@ -0,0 +1,14 @@
{
"content": {
"body": "is dancing", "format": "org.matrix.custom.html",
"formatted_body": "<strong>is dancing</strong>",
"msgtype": "m.text"
},
"event_id": "$152037280074GZeOm:localhost",
"origin_server_ts": 1520372800469,
"sender": "@example:localhost",
"type": "m.room.message",
"unsigned": {
"age": 598971425
}
}

View File

@ -0,0 +1,13 @@
{
"content": {
"name": "#tutorial:localhost"
},
"event_id": "$15139375513VdeRF:localhost",
"origin_server_ts": 1513937551461,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.name",
"unsigned": {
"age": 7034220433
}
}

View File

@ -0,0 +1,22 @@
{
"content": {},
"event_id": "$15275046980maRLj:localhost",
"origin_server_ts": 1527504698685,
"sender": "@example:localhost",
"type": "m.room.message",
"unsigned": {
"age": 19334,
"redacted_because": {
"content": {},
"event_id": "$15275047031IXQRi:localhost",
"origin_server_ts": 1527504703496,
"redacts": "$15275046980maRLj:localhost",
"sender": "@example:localhost",
"type": "m.room.redaction",
"unsigned": {
"age": 14523
}
},
"redacted_by": "$15275047031IXQRi:localhost"
}
}

View File

@ -0,0 +1,7 @@
{
"content": {},
"event_id": "$15275046980maRLj:localhost",
"origin_server_ts": 1527504698685,
"sender": "@example:localhost",
"type": "m.room.message"
}

View File

@ -0,0 +1,21 @@
{
"content": {},
"event_id": "$example_id:example.org",
"origin_server_ts": 1532324933640,
"sender": "@example:example.org",
"state_key": "test_state_key",
"type": "m.some.state",
"unsigned": {
"age": 30693154231,
"redacted_because": {
"content": {},
"event_id": "$redaction_example_id:example.org",
"origin_server_ts": 1532324940702,
"redacts": "$example_id:example.org",
"sender": "@example:example:org",
"type": "m.room.redaction",
"unsigned": {"age": 30693147169}
},
"redacted_by": "$redaction_example_id:example.org"
}
}

View File

@ -0,0 +1,10 @@
{
"content": {
"reason": "😀"
},
"event_id": "$151957878228ssqrJ:localhost",
"origin_server_ts": 1519578782185,
"sender": "@example:localhost",
"type": "m.room.redaction",
"redacts": "$151957878228ssqrj:localhost"
}

View File

@ -0,0 +1,21 @@
{
"content": {
"info": {
"h": 398,
"mimetype": "image/jpeg",
"size": 31037,
"w": 394
},
"url": "mxc://domain.com/JWEIFJgwEIhweiWJE"
},
"event_id": "$143273582443PhrSn:domain.com",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:domain.com",
"sender": "@example:domain.com",
"state_key": "",
"type": "m.room.avatar",
"unsigned": {
"age": 1234
}
}

View File

@ -0,0 +1,10 @@
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
}

View File

@ -0,0 +1,18 @@
{
"content": {
"topic": "😀"
},
"event_id": "$151957878228ssqrJ:localhost",
"origin_server_ts": 1519578782185,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.topic",
"unsigned": {
"age": 1392989709,
"prev_content": {
"topic": "test"
},
"prev_sender": "@example:localhost",
"replaces_state": "$151957069225EVYKm:localhost"
}
}

View File

@ -0,0 +1,10 @@
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}