diff --git a/Cargo.toml b/Cargo.toml index 91c6bf4..e30cb7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ description = "MUC implementation allowing participants to play the Hanabi game. [dependencies] async-trait = "^0.1" chrono = "0.4.22" +dyn-clone = "1.0.9" env_logger = "^0.9" futures = "^0.3" lazy_static = "^1.4" diff --git a/src/component.rs b/src/component.rs index f134630..8f7d115 100644 --- a/src/component.rs +++ b/src/component.rs @@ -19,7 +19,10 @@ use crate::error::Error; use std::collections::VecDeque; #[cfg(test)] use std::fmt; -use std::marker::Send; +// #[cfg(test)] +// use std::iter::IntoIterator; +#[cfg(test)] +use std::marker::{Send, Sync}; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::Context; @@ -27,6 +30,8 @@ use std::task::Context; use std::thread; use async_trait::async_trait; +#[cfg(test)] +use dyn_clone::{clone_trait_object, DynClone}; use futures::{task::Poll, Stream}; use log::debug; use tokio_xmpp::Component as TokioXMPPComponent; @@ -92,16 +97,47 @@ impl Component { } } +/* #[cfg(test)] +pub trait ExpectCb: FnOnce(T) + Send + Sync + DynClone + 'static {} +#[cfg(test)] +clone_trait_object!(ExpectCb); +#[cfg(test)] +clone_trait_object!(ExpectCb); +#[cfg(test)] +clone_trait_object!(ExpectCb); + +#[cfg(test)] +impl ExpectCb for dyn FnOnce(T) {} +*/ + +#[cfg(test)] +pub trait Foo: FnOnce(Iq) + DynClone {} +#[cfg(test)] +clone_trait_object!(Foo); +#[cfg(test)] +pub trait Bar: Foo + Send + Sync + 'static {} +#[cfg(test)] +clone_trait_object!(Bar); + +#[cfg(test)] +pub trait ExpectIq: FnOnce(Iq) + Send + Sync + DynClone + 'static {} +#[cfg(test)] +impl ExpectIq for dyn FnOnce(Iq) where Self: ExpectIq {} +#[cfg(test)] +clone_trait_object!(ExpectIq); + +#[cfg(test)] +#[derive(Clone)] enum Expect { /// Simple Element Element(TestElement), /// Callback taking an Iq, with a description alongside - Iq(Box, String), - /// Callback taking a Presence, with a description alongside - Presence(Box, String), + Iq(Box, String), /// Callback taking a Message, with a description alongside - Message(Box, String), + Message(Box, String), + /// Callback taking a Presence, with a description alongside + Presence(Box, String), } #[cfg(test)] @@ -117,6 +153,63 @@ impl fmt::Debug for Expect { } } +#[cfg(test)] +#[derive(Debug)] +pub struct Scenario { + inbuf: VecDeque, + expectbuf: VecDeque, +} + +#[cfg(test)] +impl Scenario { + pub fn new() -> Self { + Scenario { + inbuf: VecDeque::new(), + expectbuf: VecDeque::new(), + } + } + + pub fn into_inner(self) -> (VecDeque, VecDeque) { + (self.inbuf, self.expectbuf) + } + + pub fn with_input>(mut self, el: E) -> Self { + self.inbuf.push_back(el.into()); + self + } + + pub fn with_expect>(mut self, el: E) -> Self { + self.expectbuf.push_back(Expect::Element(el.into())); + self + } + + pub fn with_expect_iq>(mut self, callback: F, desc: S) -> Self { + self.expectbuf + .push_back(Expect::Iq(Box::new(callback), desc.into())); + self + } + + pub fn with_expect_message>( + mut self, + callback: F, + desc: S, + ) -> Self { + self.expectbuf + .push_back(Expect::Message(Box::new(callback), desc.into())); + self + } + + pub fn with_expect_presence>( + mut self, + callback: F, + desc: S, + ) -> Self { + self.expectbuf + .push_back(Expect::Presence(Box::new(callback), desc.into())); + self + } +} + #[cfg(test)] #[derive(Clone, Eq, PartialEq)] pub struct TestElement(pub Element); @@ -197,12 +290,7 @@ pub struct TestComponent { impl TestComponent { pub fn new(in_buffer: Vec) -> Self { TestComponent { - in_buffer: VecDeque::from( - in_buffer - .into_iter() - .map(|el| TestElement(el)) - .collect::>(), - ), + in_buffer: VecDeque::from(in_buffer.into_iter().map(TestElement).collect::>()), expect_buffer: VecDeque::new(), } } @@ -212,16 +300,12 @@ impl TestComponent { self.expect_buffer.push_back(Expect::Element(el.into())) } - pub fn expect_iq>( - &mut self, - callback: F, - desc: S, - ) { + pub fn expect_iq>(&mut self, callback: F, desc: S) { self.expect_buffer .push_back(Expect::Iq(Box::new(callback), desc.into())) } - pub fn expect_message>( + pub fn expect_message>( &mut self, callback: F, desc: S, @@ -230,7 +314,7 @@ impl TestComponent { .push_back(Expect::Message(Box::new(callback), desc.into())) } - pub fn expect_presence>( + pub fn expect_presence>( &mut self, callback: F, desc: S, @@ -239,6 +323,12 @@ impl TestComponent { .push_back(Expect::Presence(Box::new(callback), desc.into())) } + pub fn expect_scenario>(&mut self, scenario: Scenario, desc: S) { + let (input, expected) = scenario.into_inner(); + self.in_buffer.extend(input); + self.expect_buffer.extend(expected); + } + fn send_stanza_inner + Send>(&mut self, el: E) -> Result<(), Error> { let out: TestElement = el.into(); let expected = self.expect_buffer.pop_front(); diff --git a/src/main.rs b/src/main.rs index d5f77d0..6ccc23e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,8 @@ // along with this program. If not, see . #![feature(let_chains)] +#![feature(once_cell)] +// #![feature(trait_alias)] // Maybe change that someday? #![allow(clippy::result_large_err)] @@ -22,6 +24,8 @@ mod error; mod handlers; mod room; +#[cfg(test)] +mod scenarios; #[cfg(test)] mod tests; diff --git a/src/scenarios.rs b/src/scenarios.rs new file mode 100644 index 0000000..094edac --- /dev/null +++ b/src/scenarios.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2022-2099 The crate authors. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU Affero General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License +// for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::component::Scenario; + +use std::str::FromStr; +use std::sync::LazyLock; + +use lazy_static::lazy_static; +use xmpp_parsers::BareJid; + +/* +lazy_static! { + static ref COMPONENT_JID: BareJid = BareJid::from_str("commons.social").unwrap(); + static ref ROOM1: BareJid = COMPONENT_JID.with_node("direct-action"); + + /// https://en.wikipedia.org/wiki/Louise_Michel + static ref LOUISE_BARE: BareJid = BareJid::from_str("louise@example.net").unwrap(); + static ref LOUISE_NICK: String = String::from("louise"); + + /// https://en.wikipedia.org/wiki/Kanno_Sugako + static ref SUGAKO_BARE: BareJid = BareJid::from_str("すがこ@example.net").unwrap(); + static ref SUGAKO_NICK: String = String::from("すがこ"); + + /// https://en.wikipedia.org/wiki/Rosa_Luxemburg + static ref ROSA_BARE: BareJid = BareJid::from_str("rosa@example.net").unwrap(); + static ref ROSA_NICK: String = String::from("rosa"); + + /// https://en.wikipedia.org/wiki/Peter_Kropotkin + static ref PETER_BARE: BareJid = BareJid::from_str("peter@example.net").unwrap(); + static ref PETER_NICK: String = String::from("peter"); +*/ + +/// Creates a room of one participant +// pub static CREATE_ROOM_1: LazyLock = LazyLock::new(|| Scenario::new()); +lazy_static! { + pub static ref CREATE_ROOM_1: Scenario = Scenario::new(); +} diff --git a/src/tests/presence.rs b/src/tests/presence.rs index d3cda3d..4d5e3fa 100644 --- a/src/tests/presence.rs +++ b/src/tests/presence.rs @@ -16,9 +16,11 @@ use crate::component::TestComponent; use crate::handlers::handle_stanza; use crate::room::Room; +use crate::scenarios::CREATE_ROOM_1; use std::collections::{BTreeMap, HashMap}; use std::str::FromStr; +use std::sync::LazyLock; use lazy_static::lazy_static; use xmpp_parsers::{ @@ -56,6 +58,9 @@ async fn test_join_presence_empty_room() { let mut component = TestComponent::new(vec![join]); let mut rooms: HashMap = HashMap::new(); + // let create_room = LazyLock::force(&CREATE_ROOM_1); + component.expect_scenario(*CREATE_ROOM_1, "Create room of 1 participant"); + // Room is empty so there should be: // - No presence sent except for self-presence // - No message history