Compare commits
12 commits
main
...
component-
Author | SHA1 | Date | |
---|---|---|---|
ad5ece8ad5 | |||
e338bff920 | |||
db7237069c | |||
7a687df552 | |||
919f3cf754 | |||
4ec1aa42b4 | |||
e7d31e41dc | |||
562aadb488 | |||
44bec0e232 | |||
c884f38dc2 | |||
7554ff4d7c | |||
ef03c1b032 |
9 changed files with 965 additions and 272 deletions
11
.woodpecker.yml
Normal file
11
.woodpecker.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
pipeline:
|
||||||
|
lint:
|
||||||
|
image: rustlang/rust:nightly-alpine
|
||||||
|
commands:
|
||||||
|
- cargo fmt --check
|
||||||
|
test:
|
||||||
|
image: rustlang/rust:nightly-alpine
|
||||||
|
commands:
|
||||||
|
- RUST_BACKTRACE=1 cargo test
|
12
README.md
12
README.md
|
@ -13,13 +13,13 @@ file.
|
||||||
|
|
||||||
### XMPP
|
### XMPP
|
||||||
|
|
||||||
- [ ] Join
|
- [x] Join
|
||||||
* [x] Single session
|
* [x] Normal sessions
|
||||||
* [ ] Multiple sessions non-MSN
|
* [x] MSN
|
||||||
* [ ] MSN
|
|
||||||
- [ ] Presence
|
- [ ] Presence
|
||||||
* [ ] Probes
|
* [x] Updates
|
||||||
* [ ] Resync
|
* [x] Resync
|
||||||
|
* [ ] Probes (storing updates to answer probes)
|
||||||
- [ ] Iq
|
- [ ] Iq
|
||||||
* [ ] Ping answers
|
* [ ] Ping answers
|
||||||
* [ ] Ping probes?
|
* [ ] Ping probes?
|
||||||
|
|
173
src/component.rs
173
src/component.rs
|
@ -12,6 +12,7 @@ use std::marker::Send;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::Context;
|
use std::task::Context;
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use futures::{task::Poll, Stream};
|
use futures::{task::Poll, Stream};
|
||||||
|
@ -78,10 +79,14 @@ impl Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Expect {
|
enum Expect {
|
||||||
Element(Element),
|
/// Simple Element
|
||||||
Iq(Box<dyn FnOnce(Iq) + Send + 'static>),
|
Element(TestElement),
|
||||||
Presence(Box<dyn FnOnce(Presence) + Send + 'static>),
|
/// Callback taking an Iq, with a description alongside
|
||||||
Message(Box<dyn FnOnce(Message) + Send + 'static>),
|
Iq(Box<dyn FnOnce(Iq) + Send + 'static>, String),
|
||||||
|
/// Callback taking a Presence, with a description alongside
|
||||||
|
Presence(Box<dyn FnOnce(Presence) + Send + 'static>, String),
|
||||||
|
/// Callback taking a Message, with a description alongside
|
||||||
|
Message(Box<dyn FnOnce(Message) + Send + 'static>, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Expect {
|
impl fmt::Debug for Expect {
|
||||||
|
@ -89,75 +94,138 @@ impl fmt::Debug for Expect {
|
||||||
write!(f, "Expect::")?;
|
write!(f, "Expect::")?;
|
||||||
match self {
|
match self {
|
||||||
Expect::Element(el) => write!(f, "Element({:?})", String::from(el)),
|
Expect::Element(el) => write!(f, "Element({:?})", String::from(el)),
|
||||||
Expect::Iq(_) => write!(f, "Iq(<cb>)"),
|
Expect::Iq(_, desc) => write!(f, "Iq(<cb>, {})", desc),
|
||||||
Expect::Message(_) => write!(f, "Message(<cb>)"),
|
Expect::Message(_, desc) => write!(f, "Message(<cb>, {})", desc),
|
||||||
Expect::Presence(_) => write!(f, "Presence(<cb>)"),
|
Expect::Presence(_, desc) => write!(f, "Presence(<cb>, {})", desc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct TestElement(pub Element);
|
||||||
|
|
||||||
|
impl Deref for TestElement {
|
||||||
|
type Target = Element;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TestElement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", String::from(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TestElement {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", String::from(&self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TestElement> for String {
|
||||||
|
fn from(elem: &TestElement) -> Self {
|
||||||
|
format!("{}", elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Element> for TestElement {
|
||||||
|
fn from(elem: Element) -> Self {
|
||||||
|
Self(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TestElement> for Element {
|
||||||
|
fn from(elem: TestElement) -> Self {
|
||||||
|
elem.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Iq> for TestElement {
|
||||||
|
fn from(elem: Iq) -> Self {
|
||||||
|
Self(Element::from(elem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Presence> for TestElement {
|
||||||
|
fn from(elem: Presence) -> Self {
|
||||||
|
Self(Element::from(elem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Message> for TestElement {
|
||||||
|
fn from(elem: Message) -> Self {
|
||||||
|
Self(Element::from(elem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TestComponent {
|
pub struct TestComponent {
|
||||||
in_buffer: VecDeque<Element>,
|
in_buffer: VecDeque<TestElement>,
|
||||||
out_buffer: VecDeque<Element>,
|
|
||||||
expect_buffer: VecDeque<Expect>,
|
expect_buffer: VecDeque<Expect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestComponent {
|
impl TestComponent {
|
||||||
pub fn new(in_buffer: Vec<Element>) -> Self {
|
pub fn new(in_buffer: Vec<Element>) -> Self {
|
||||||
TestComponent {
|
TestComponent {
|
||||||
in_buffer: VecDeque::from(in_buffer),
|
in_buffer: VecDeque::from(
|
||||||
out_buffer: VecDeque::new(),
|
in_buffer
|
||||||
|
.into_iter()
|
||||||
|
.map(|el| TestElement(el))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
),
|
||||||
expect_buffer: VecDeque::new(),
|
expect_buffer: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds elements to be expected, in the order they're being added
|
/// Adds elements to be expected, in the order they're being added
|
||||||
pub fn expect<E: Into<Element>>(&mut self, el: E) {
|
pub fn expect<E: Into<TestElement>>(&mut self, el: E) {
|
||||||
self.expect_buffer.push_back(Expect::Element(el.into()))
|
self.expect_buffer.push_back(Expect::Element(el.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_iq<F: FnOnce(Iq) + Send + 'static>(&mut self, callback: F) {
|
pub fn expect_iq<F: FnOnce(Iq) + Send + 'static, S: Into<String>>(
|
||||||
self.expect_buffer.push_back(Expect::Iq(Box::new(callback)))
|
&mut self,
|
||||||
}
|
callback: F,
|
||||||
|
desc: S,
|
||||||
pub fn expect_message<F: FnOnce(Message) + Send + 'static>(&mut self, callback: F) {
|
) {
|
||||||
self.expect_buffer
|
self.expect_buffer
|
||||||
.push_back(Expect::Message(Box::new(callback)))
|
.push_back(Expect::Iq(Box::new(callback), desc.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expect_presence<F: FnOnce(Presence) + Send + 'static>(&mut self, callback: F) {
|
pub fn expect_message<F: FnOnce(Message) + Send + 'static, S: Into<String>>(
|
||||||
|
&mut self,
|
||||||
|
callback: F,
|
||||||
|
desc: S,
|
||||||
|
) {
|
||||||
self.expect_buffer
|
self.expect_buffer
|
||||||
.push_back(Expect::Presence(Box::new(callback)))
|
.push_back(Expect::Message(Box::new(callback), desc.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts expected output and actual output are the same
|
pub fn expect_presence<F: FnOnce(Presence) + Send + 'static, S: Into<String>>(
|
||||||
pub fn assert(&mut self) {
|
&mut self,
|
||||||
loop {
|
callback: F,
|
||||||
let out = self.out_buffer.pop_front();
|
desc: S,
|
||||||
let expected = self.expect_buffer.pop_front();
|
) {
|
||||||
|
self.expect_buffer
|
||||||
|
.push_back(Expect::Presence(Box::new(callback), desc.into()))
|
||||||
|
}
|
||||||
|
|
||||||
match (out, expected) {
|
fn _send_stanza<E: Into<TestElement> + Send>(&mut self, el: E) -> Result<(), Error> {
|
||||||
(None, None) => break,
|
let out: TestElement = el.into();
|
||||||
(Some(out), Some(expected)) => match expected {
|
let expected = self.expect_buffer.pop_front();
|
||||||
Expect::Element(el) => assert_eq!(String::from(&el), String::from(&out)),
|
|
||||||
Expect::Iq(cb) => cb(Iq::try_from(out).unwrap()),
|
match expected {
|
||||||
Expect::Message(cb) => cb(Message::try_from(out).unwrap()),
|
Some(expected) => match expected {
|
||||||
Expect::Presence(cb) => cb(Presence::try_from(out).unwrap()),
|
Expect::Element(el) => assert_eq!(String::from(&el), String::from(&out)),
|
||||||
},
|
Expect::Iq(cb, _) => cb(Iq::try_from(out.0).unwrap()),
|
||||||
(Some(out), None) => panic!("Missing matching expected element: {:?}", out),
|
Expect::Message(cb, _) => cb(Message::try_from(out.0).unwrap()),
|
||||||
(None, Some(expected)) => match expected {
|
Expect::Presence(cb, _) => cb(Presence::try_from(out.0).unwrap()),
|
||||||
Expect::Element(el) => panic!("Missing matching sent element: {:?}", el),
|
},
|
||||||
Expect::Iq(_) => panic!("Missing matching sent iq"),
|
None => panic!("Missing matching expected element: {:?}", out),
|
||||||
Expect::Message(_) => panic!("Missing matching sent message"),
|
|
||||||
Expect::Presence(_) => panic!("Missing matching sent presence"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn _send_stanza<E: Into<Element> + Send>(&mut self, el: E) -> Result<(), Error> {
|
Ok(())
|
||||||
Ok(self.out_buffer.push_back(el.into()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,23 +234,34 @@ impl Stream for TestComponent {
|
||||||
|
|
||||||
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
|
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
|
||||||
while self.in_buffer.len() > 0 {
|
while self.in_buffer.len() > 0 {
|
||||||
return Poll::Ready(self.in_buffer.pop_front());
|
return Poll::Ready(self.in_buffer.pop_front().map(|el| el.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
Poll::Ready(None)
|
Poll::Ready(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for TestComponent {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Don't assert if we're already panicking. Rustc displays a huge backtrace when "panicked
|
||||||
|
// while panicking" even when nobody asks for it (RUST_BACKTRACE unset). Let the error
|
||||||
|
// appear if there isn't any other error.
|
||||||
|
if ! thread::panicking() {
|
||||||
|
assert_eq!(self.expect_buffer.len(), 0, "Remaining expected elements in the buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ComponentTrait for TestComponent {
|
impl ComponentTrait for TestComponent {
|
||||||
async fn send_stanza<E: Into<Element> + Send>(&mut self, el: E) -> Result<(), Error> {
|
async fn send_stanza<E: Into<Element> + Send>(&mut self, el: E) -> Result<(), Error> {
|
||||||
self._send_stanza(el)
|
self._send_stanza(el.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ComponentTrait for &mut TestComponent {
|
impl ComponentTrait for &mut TestComponent {
|
||||||
async fn send_stanza<E: Into<Element> + Send>(&mut self, el: E) -> Result<(), Error> {
|
async fn send_stanza<E: Into<Element> + Send>(&mut self, el: E) -> Result<(), Error> {
|
||||||
self._send_stanza(el)
|
self._send_stanza(el.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub enum Error {
|
||||||
MismatchJids(Jid, Jid),
|
MismatchJids(Jid, Jid),
|
||||||
NickAlreadyAssigned(String),
|
NickAlreadyAssigned(String),
|
||||||
NonexistantSession(FullJid),
|
NonexistantSession(FullJid),
|
||||||
|
SessionAlreadyExists(FullJid),
|
||||||
XMPPError(TokioXMPPError),
|
XMPPError(TokioXMPPError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ impl fmt::Display for Error {
|
||||||
Error::MismatchJids(jid1, jid2) => write!(f, "Mismatch Jids: {}, {}", jid1, jid2),
|
Error::MismatchJids(jid1, jid2) => write!(f, "Mismatch Jids: {}, {}", jid1, jid2),
|
||||||
Error::NickAlreadyAssigned(err) => write!(f, "Nickname already assigned: {}", err),
|
Error::NickAlreadyAssigned(err) => write!(f, "Nickname already assigned: {}", err),
|
||||||
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err),
|
Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err),
|
||||||
|
Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {}", err),
|
||||||
Error::XMPPError(err) => write!(f, "XMPP error: {}", err),
|
Error::XMPPError(err) => write!(f, "XMPP error: {}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -175,7 +175,7 @@ async fn handle_presence<C: ComponentTrait>(
|
||||||
).into()
|
).into()
|
||||||
]);
|
]);
|
||||||
if let Some(mut room) = rooms.remove(&roomjid) {
|
if let Some(mut room) = rooms.remove(&roomjid) {
|
||||||
match room.remove_session(component, realjid).await {
|
match room.remove_session(component, realjid, participant.resource.clone()).await {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(Error::NonexistantSession(_)) => {
|
Err(Error::NonexistantSession(_)) => {
|
||||||
component.send_stanza(error).await.unwrap();
|
component.send_stanza(error).await.unwrap();
|
||||||
|
|
680
src/room.rs
680
src/room.rs
|
@ -16,7 +16,7 @@
|
||||||
use crate::component::ComponentTrait;
|
use crate::component::ComponentTrait;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::BTreeMap;
|
||||||
use std::iter::IntoIterator;
|
use std::iter::IntoIterator;
|
||||||
|
|
||||||
use chrono::{FixedOffset, Utc};
|
use chrono::{FixedOffset, Utc};
|
||||||
|
@ -34,11 +34,25 @@ use xmpp_parsers::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Nick = String;
|
pub type Nick = String;
|
||||||
|
type Session = FullJid;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum BroadcastPresence {
|
||||||
|
/// Resource joined the room. It needs to know about all other participants, and other
|
||||||
|
/// participants needs to know about it.
|
||||||
|
Join,
|
||||||
|
/// Resource lost sync. It needs to know about all other participants.
|
||||||
|
Resync,
|
||||||
|
/// Resource change status (becomes busy, etc.), tell all other participants.
|
||||||
|
Update,
|
||||||
|
/// Resource leaves. It only needs confirmation that it leaves. Tell all other participants.
|
||||||
|
Leave,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
pub jid: BareJid,
|
pub jid: BareJid,
|
||||||
pub occupants: HashMap<BareJid, Occupant>,
|
pub occupants: BTreeMap<Nick, Occupant>,
|
||||||
// TODO: Subject struct.
|
// TODO: Subject struct.
|
||||||
// TODO: Store subject lang
|
// TODO: Store subject lang
|
||||||
pub subject: Option<(String, Occupant, DateTime)>,
|
pub subject: Option<(String, Occupant, DateTime)>,
|
||||||
|
@ -48,107 +62,235 @@ impl Room {
|
||||||
pub fn new(jid: BareJid) -> Self {
|
pub fn new(jid: BareJid) -> Self {
|
||||||
Room {
|
Room {
|
||||||
jid,
|
jid,
|
||||||
occupants: HashMap::new(),
|
occupants: BTreeMap::new(),
|
||||||
subject: None,
|
subject: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn broadcast_presence<C: ComponentTrait>(
|
||||||
|
&self,
|
||||||
|
component: &mut C,
|
||||||
|
own_occupant: &Occupant,
|
||||||
|
own_session: &Session,
|
||||||
|
mode: BroadcastPresence,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let leave = mode == BroadcastPresence::Leave;
|
||||||
|
|
||||||
|
// All participants to new participant
|
||||||
|
let presence_to_new = Presence::new(
|
||||||
|
if leave { PresenceType::Unavailable } else { PresenceType::None }
|
||||||
|
)
|
||||||
|
.with_to(own_session.clone())
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: Vec::new(),
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}
|
||||||
|
.into()]);
|
||||||
|
|
||||||
|
// New participant to all other sessions
|
||||||
|
let presence_to_old = Presence::new(
|
||||||
|
if leave { PresenceType::Unavailable } else { PresenceType::None }
|
||||||
|
)
|
||||||
|
.with_from(Jid::Full(own_occupant.participant.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: Vec::new(),
|
||||||
|
items: vec![
|
||||||
|
MucItem::new(
|
||||||
|
Affiliation::Owner,
|
||||||
|
if leave { Role::None } else { Role::Moderator },
|
||||||
|
)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
.into()]);
|
||||||
|
|
||||||
|
let sync = match mode {
|
||||||
|
BroadcastPresence::Join |
|
||||||
|
BroadcastPresence::Resync => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
let update = match mode {
|
||||||
|
BroadcastPresence::Join |
|
||||||
|
BroadcastPresence::Update => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (_, other) in self.occupants.iter() {
|
||||||
|
if own_occupant.nick == other.nick {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if sync { // Send presences from others to participant.
|
||||||
|
let presence = presence_to_new.clone()
|
||||||
|
.with_from(Jid::Full(other.participant.clone()));
|
||||||
|
component.send_stanza(presence).await?;
|
||||||
|
}
|
||||||
|
if update || leave { // Send presence from participant to others.
|
||||||
|
for session in other.iter() {
|
||||||
|
// Skip sending if it's us.
|
||||||
|
if session == own_session {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let presence = presence_to_old.clone()
|
||||||
|
.with_to(Jid::Full(session.clone()));
|
||||||
|
component.send_stanza(presence).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MucItems to be sent to sessions of this occupant
|
||||||
|
let self_items = own_occupant.iter().map(|session| {
|
||||||
|
MucItem {
|
||||||
|
affiliation: Affiliation::Owner,
|
||||||
|
role: if leave { Role::None } else { Role::Moderator },
|
||||||
|
jid: Some(session.clone()),
|
||||||
|
nick: None,
|
||||||
|
actor: None,
|
||||||
|
continue_: None,
|
||||||
|
reason: None,
|
||||||
|
}
|
||||||
|
}).collect::<Vec<MucItem>>();
|
||||||
|
|
||||||
|
// Multi-Session Nick: For this occupant, include all sessions all with item@jid discovered
|
||||||
|
// so they can identify each other as being the same account under the same nick.
|
||||||
|
let session_presence = Presence::new(
|
||||||
|
if leave { PresenceType::Unavailable } else { PresenceType::None }
|
||||||
|
)
|
||||||
|
.with_from(Jid::Full(own_occupant.participant.clone()));
|
||||||
|
|
||||||
|
for session in own_occupant.iter() {
|
||||||
|
if session == own_session {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let presence = session_presence.clone()
|
||||||
|
.with_to(Jid::Full(session.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: self_items.clone(),
|
||||||
|
}.into()]);
|
||||||
|
component.send_stanza(presence).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send self-presence
|
||||||
|
if sync || leave {
|
||||||
|
// New participant to all other sessions
|
||||||
|
let self_presence = Presence::new(
|
||||||
|
if leave { PresenceType::Unavailable } else { PresenceType::None }
|
||||||
|
)
|
||||||
|
.with_from(Jid::Full(own_occupant.participant.clone()))
|
||||||
|
.with_to(own_session.clone())
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: if leave { vec![
|
||||||
|
MucStatus::SelfPresence,
|
||||||
|
] } else {
|
||||||
|
vec![
|
||||||
|
MucStatus::SelfPresence,
|
||||||
|
MucStatus::AssignedNick,
|
||||||
|
] },
|
||||||
|
items: if leave {
|
||||||
|
vec![MucItem::new(Affiliation::Owner, Role::None)]
|
||||||
|
} else {
|
||||||
|
self_items
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.into()]);
|
||||||
|
component.send_stanza(self_presence).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_subject<C: ComponentTrait>(
|
||||||
|
&mut self,
|
||||||
|
component: &mut C,
|
||||||
|
realjid: Session,
|
||||||
|
occupant: Occupant,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
debug!("Sending subject!");
|
||||||
|
if self.subject.is_none() {
|
||||||
|
let subject = String::from("");
|
||||||
|
let setter = occupant;
|
||||||
|
let stamp = DateTime(Utc::now().with_timezone(&FixedOffset::east(0)));
|
||||||
|
self.subject = Some((subject, setter, stamp));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut subject = Message::new(Some(Jid::Full(realjid)));
|
||||||
|
subject.from = Some(Jid::Full(
|
||||||
|
self.subject.as_ref().unwrap().1.participant.clone(),
|
||||||
|
));
|
||||||
|
subject.subjects.insert(
|
||||||
|
String::from("en"),
|
||||||
|
Subject(self.subject.as_ref().unwrap().0.clone()),
|
||||||
|
);
|
||||||
|
subject.type_ = MessageType::Groupchat;
|
||||||
|
subject.payloads = vec![Delay {
|
||||||
|
from: Some(Jid::Bare(self.jid.clone())),
|
||||||
|
stamp: self.subject.as_ref().unwrap().2.clone(),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
.into()];
|
||||||
|
component.send_stanza(subject).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn add_session<C: ComponentTrait>(
|
pub async fn add_session<C: ComponentTrait>(
|
||||||
&mut self,
|
&mut self,
|
||||||
component: &mut C,
|
component: &mut C,
|
||||||
realjid: FullJid,
|
realjid: Session,
|
||||||
nick: Nick,
|
new_nick: Nick,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let bare = BareJid::from(realjid.clone());
|
// Ensure nick isn't already assigned
|
||||||
if let Some(occupant) = self.occupants.get_mut(&bare) {
|
let _ = self.occupants.iter().try_for_each(|(nick, occupant)| {
|
||||||
occupant.add_session(realjid)?;
|
let new_nick = new_nick.as_str();
|
||||||
} else {
|
if new_nick == nick && occupant.real != BareJid::from(realjid.clone()) {
|
||||||
debug!("{} is joining {}", realjid, self.jid);
|
return Err(Error::NickAlreadyAssigned(String::from(new_nick)));
|
||||||
|
|
||||||
let new_occupant = Occupant::new(&self, realjid.clone(), nick.clone());
|
|
||||||
|
|
||||||
// Ensure nick isn't already assigned
|
|
||||||
let _ = self.occupants.iter().try_for_each(|(_, occupant)| {
|
|
||||||
let nick = nick.clone();
|
|
||||||
if occupant.nick == nick {
|
|
||||||
return Err(Error::NickAlreadyAssigned(nick));
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Send occupants
|
|
||||||
debug!("Sending occupants for {}", realjid);
|
|
||||||
// Other participants to new participant
|
|
||||||
let presence_to_new = Presence::new(PresenceType::None)
|
|
||||||
// New occupant with a single session
|
|
||||||
.with_to(new_occupant.sessions[0].clone())
|
|
||||||
.with_payloads(vec![MucUser {
|
|
||||||
status: Vec::new(),
|
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
|
||||||
}
|
|
||||||
.into()]);
|
|
||||||
// New participant to other participants
|
|
||||||
let presence_to_old = Presence::new(PresenceType::None)
|
|
||||||
.with_from(Jid::Full(new_occupant.participant.clone()))
|
|
||||||
.with_payloads(vec![MucUser {
|
|
||||||
status: Vec::new(),
|
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
|
||||||
}
|
|
||||||
.into()]);
|
|
||||||
for (_, occupant) in self.occupants.iter() {
|
|
||||||
component
|
|
||||||
.send_stanza(
|
|
||||||
presence_to_new
|
|
||||||
.clone()
|
|
||||||
.with_from(occupant.participant.clone()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
for session in occupant.iter() {
|
|
||||||
component
|
|
||||||
.send_stanza(presence_to_old.clone().with_to(session.clone()))
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
// Add into occupants
|
let mode: Option<BroadcastPresence> = {
|
||||||
let _ = self.occupants.insert(bare.clone(), new_occupant.clone());
|
if let Some(occupant) = self.occupants.get_mut(&new_nick) {
|
||||||
|
match occupant.add_session(realjid.clone()) {
|
||||||
// Self-presence
|
Ok(_) => {
|
||||||
debug!("Sending self-presence for {}", realjid);
|
Some(BroadcastPresence::Join)
|
||||||
let participant: FullJid = self.jid.clone().with_resource(nick);
|
},
|
||||||
let status = vec![MucStatus::SelfPresence, MucStatus::AssignedNick];
|
Err(Error::SessionAlreadyExists(_)) => {
|
||||||
let items = vec![MucItem::new(Affiliation::Owner, Role::Moderator)];
|
Some(BroadcastPresence::Resync)
|
||||||
let self_presence = Presence::new(PresenceType::None)
|
},
|
||||||
.with_from(participant.clone())
|
Err(err) => return Err(err),
|
||||||
.with_to(realjid.clone())
|
}
|
||||||
.with_payloads(vec![MucUser { status, items }.into()]);
|
} else {
|
||||||
component.send_stanza(self_presence).await?;
|
Some(BroadcastPresence::Join)
|
||||||
|
|
||||||
// Send subject
|
|
||||||
debug!("Sending subject!");
|
|
||||||
if self.subject.is_none() {
|
|
||||||
let subject = String::from("");
|
|
||||||
let setter = new_occupant;
|
|
||||||
let stamp = DateTime(Utc::now().with_timezone(&FixedOffset::east(0)));
|
|
||||||
self.subject = Some((subject, setter, stamp));
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut subject = Message::new(Some(Jid::Full(realjid)));
|
if ! self.occupants.contains_key(&new_nick) {
|
||||||
subject.from = Some(Jid::Full(
|
let _ = self.occupants.insert(
|
||||||
self.subject.as_ref().unwrap().1.participant.clone(),
|
new_nick.clone(),
|
||||||
));
|
Occupant::new(&self, realjid.clone(), new_nick.clone()),
|
||||||
subject.subjects.insert(
|
|
||||||
String::from("en"),
|
|
||||||
Subject(self.subject.as_ref().unwrap().0.clone()),
|
|
||||||
);
|
);
|
||||||
subject.type_ = MessageType::Groupchat;
|
}
|
||||||
subject.payloads = vec![Delay {
|
let occupant = self.occupants.get(&new_nick).unwrap();
|
||||||
from: Some(Jid::Bare(self.jid.clone())),
|
|
||||||
stamp: self.subject.as_ref().unwrap().2.clone(),
|
match mode {
|
||||||
data: None,
|
Some(BroadcastPresence::Resync) => {
|
||||||
}
|
self.broadcast_presence(
|
||||||
.into()];
|
component,
|
||||||
component.send_stanza(subject).await?;
|
&occupant,
|
||||||
|
&realjid,
|
||||||
|
BroadcastPresence::Resync,
|
||||||
|
).await?;
|
||||||
|
},
|
||||||
|
Some(BroadcastPresence::Join) => {
|
||||||
|
debug!("{} is joining {}", realjid, self.jid);
|
||||||
|
|
||||||
|
self.broadcast_presence(
|
||||||
|
component,
|
||||||
|
&occupant,
|
||||||
|
&realjid,
|
||||||
|
BroadcastPresence::Join,
|
||||||
|
).await?;
|
||||||
|
self.send_subject(component, realjid, occupant.clone()).await?;
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -157,56 +299,34 @@ impl Room {
|
||||||
pub async fn remove_session<C: ComponentTrait>(
|
pub async fn remove_session<C: ComponentTrait>(
|
||||||
&mut self,
|
&mut self,
|
||||||
component: &mut C,
|
component: &mut C,
|
||||||
realjid: FullJid,
|
realjid: Session,
|
||||||
|
nick: Nick,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let bare = BareJid::from(realjid.clone());
|
|
||||||
|
|
||||||
// If occupant doesn't exist, ignore.
|
// If occupant doesn't exist, ignore.
|
||||||
if let Some(mut occupant) = self.occupants.remove(&bare) {
|
if let Some(mut occupant) = self.occupants.remove(&nick) {
|
||||||
let self_presence = Presence::new(PresenceType::Unavailable)
|
self.broadcast_presence(
|
||||||
.with_from(occupant.participant.clone())
|
component,
|
||||||
.with_to(realjid.clone())
|
&occupant,
|
||||||
.with_payloads(vec![MucUser {
|
&realjid,
|
||||||
status: vec![MucStatus::SelfPresence],
|
BroadcastPresence::Leave,
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
).await?;
|
||||||
}
|
|
||||||
.into()]);
|
|
||||||
|
|
||||||
component.send_stanza(self_presence).await?;
|
|
||||||
occupant.remove_session(realjid)?;
|
occupant.remove_session(realjid)?;
|
||||||
|
} else {
|
||||||
let presence = Presence::new(PresenceType::Unavailable)
|
// TODO: Error
|
||||||
.with_from(occupant.participant.clone())
|
|
||||||
.with_payloads(vec![MucUser {
|
|
||||||
status: Vec::new(),
|
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
|
||||||
}
|
|
||||||
.into()]);
|
|
||||||
|
|
||||||
// Add remaining occupant sessions in the occupant pool
|
|
||||||
if occupant.sessions.len() > 0 {
|
|
||||||
self.occupants.insert(bare, occupant);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, occupant) in self.occupants.iter() {
|
|
||||||
for session in occupant.iter() {
|
|
||||||
let presence = presence.clone().with_to(Jid::Full(session.clone()));
|
|
||||||
component.send_stanza(presence).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Occupant {
|
pub struct Occupant {
|
||||||
/// Public Jid for the Occupant
|
/// Public Jid for the Occupant
|
||||||
real: BareJid,
|
pub real: BareJid,
|
||||||
participant: FullJid,
|
pub participant: FullJid,
|
||||||
nick: Nick,
|
pub nick: Nick,
|
||||||
sessions: Vec<FullJid>,
|
pub sessions: Vec<FullJid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Occupant {
|
impl Occupant {
|
||||||
|
@ -224,6 +344,13 @@ impl Occupant {
|
||||||
return Err(Error::MismatchJids(Jid::from(self.real.clone()), Jid::from(real.clone())));
|
return Err(Error::MismatchJids(Jid::from(self.real.clone()), Jid::from(real.clone())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for session in &self.sessions {
|
||||||
|
if &real == session {
|
||||||
|
return Err(Error::SessionAlreadyExists(real))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.sessions.push(real);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,14 +390,293 @@ impl Occupant {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use xmpp_parsers::{BareJid, FullJid};
|
use crate::component::TestComponent;
|
||||||
|
use xmpp_parsers::{
|
||||||
|
BareJid, Element,
|
||||||
|
presence::{Presence, Type as PresenceType},
|
||||||
|
muc::{
|
||||||
|
MucUser,
|
||||||
|
user::{Affiliation, Role, Item as MucItem, Status as MucStatus},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
#[test]
|
#[tokio::test]
|
||||||
fn occupant_ignore_dup_session() {
|
async fn test_broadcast_presence_resync() {
|
||||||
let room = Room::new(BareJid::from_str("room@muc").unwrap());
|
let roomjid = BareJid::from_str("room@muc").unwrap();
|
||||||
let real = FullJid::from_str("foo@bar/meh").unwrap();
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
let mut occupant = Occupant::new(&room, real.clone(), String::from("nick"));
|
let participant1 = roomjid.clone().with_resource(String::from("nick1"));
|
||||||
occupant.add_session(real.clone()).unwrap();
|
let realjid2 = FullJid::from_str("qxx@foo/bar").unwrap();
|
||||||
assert_eq!(occupant.iter().count(), 1);
|
let participant2 = roomjid.clone().with_resource(String::from("nick2"));
|
||||||
|
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
|
||||||
|
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
|
||||||
|
|
||||||
|
let mut room = Room::new(roomjid.clone());
|
||||||
|
|
||||||
|
room.occupants = BTreeMap::new();
|
||||||
|
room.occupants.insert(
|
||||||
|
participant1.resource.clone(), Occupant::new(&room, realjid1.clone(), String::from("nick1")),
|
||||||
|
);
|
||||||
|
room.occupants.insert(
|
||||||
|
participant2.resource.clone(), Occupant::new(&room, realjid2.clone(), String::from("nick2")),
|
||||||
|
);
|
||||||
|
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
|
||||||
|
room.occupants.insert(participant3.resource.clone(), occupant3.clone());
|
||||||
|
|
||||||
|
let self_item = MucItem::try_from(
|
||||||
|
r#"<item xmlns="http://jabber.org/protocol/muc#user" affiliation="owner" role="moderator" jid="bar@qxx/foo"/>"#
|
||||||
|
.parse::<Element>()
|
||||||
|
.unwrap()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// BroadcastPresence::Resync
|
||||||
|
let mut component = TestComponent::new(vec![]);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant1.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant2.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
|
items: vec![self_item.clone()],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast_presence(&mut component, &occupant3, &realjid3, BroadcastPresence::Resync).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_broadcast_presence_update() {
|
||||||
|
let roomjid = BareJid::from_str("room@muc").unwrap();
|
||||||
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
let participant1 = roomjid.clone().with_resource(String::from("nick1"));
|
||||||
|
let realjid2 = FullJid::from_str("qxx@foo/bar").unwrap();
|
||||||
|
let participant2 = roomjid.clone().with_resource(String::from("nick2"));
|
||||||
|
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
|
||||||
|
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
|
||||||
|
|
||||||
|
let mut room = Room::new(roomjid.clone());
|
||||||
|
|
||||||
|
room.occupants = BTreeMap::new();
|
||||||
|
room.occupants.insert(
|
||||||
|
participant1.resource.clone(), Occupant::new(&room, realjid1.clone(), String::from("nick1")),
|
||||||
|
);
|
||||||
|
room.occupants.insert(
|
||||||
|
participant2.resource.clone(), Occupant::new(&room, realjid2.clone(), String::from("nick2")),
|
||||||
|
);
|
||||||
|
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
|
||||||
|
room.occupants.insert(participant3.resource.clone(), occupant3.clone());
|
||||||
|
|
||||||
|
// BroadcastPresence::Update
|
||||||
|
let mut component = TestComponent::new(vec![]);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid1.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid2.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast_presence(&mut component, &occupant3, &realjid3, BroadcastPresence::Update).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_broadcast_presence_join() {
|
||||||
|
let roomjid = BareJid::from_str("room@muc").unwrap();
|
||||||
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
let participant1 = roomjid.clone().with_resource(String::from("nick1"));
|
||||||
|
let realjid2 = FullJid::from_str("qxx@foo/bar").unwrap();
|
||||||
|
let participant2 = roomjid.clone().with_resource(String::from("nick2"));
|
||||||
|
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
|
||||||
|
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
|
||||||
|
|
||||||
|
let mut room = Room::new(roomjid.clone());
|
||||||
|
|
||||||
|
room.occupants = BTreeMap::new();
|
||||||
|
room.occupants.insert(
|
||||||
|
participant1.resource.clone(), Occupant::new(&room, realjid1.clone(), String::from("nick1")),
|
||||||
|
);
|
||||||
|
room.occupants.insert(
|
||||||
|
participant2.resource.clone(), Occupant::new(&room, realjid2.clone(), String::from("nick2")),
|
||||||
|
);
|
||||||
|
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
|
||||||
|
room.occupants.insert(participant3.resource.clone(), occupant3.clone());
|
||||||
|
|
||||||
|
let self_item = MucItem::try_from(
|
||||||
|
r#"<item xmlns="http://jabber.org/protocol/muc#user" affiliation="owner" role="moderator" jid="bar@qxx/foo"/>"#
|
||||||
|
.parse::<Element>()
|
||||||
|
.unwrap()
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
// BroadcastPresence::Join
|
||||||
|
let mut component = TestComponent::new(vec![]);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant1.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid1.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant2.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid2.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
|
items: vec![self_item],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast_presence(&mut component, &occupant3, &realjid3, BroadcastPresence::Join).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_broadcast_presence_leave() {
|
||||||
|
let roomjid = BareJid::from_str("room@muc").unwrap();
|
||||||
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
let participant1 = roomjid.clone().with_resource(String::from("nick1"));
|
||||||
|
let realjid2 = FullJid::from_str("qxx@foo/bar").unwrap();
|
||||||
|
let participant2 = roomjid.clone().with_resource(String::from("nick2"));
|
||||||
|
let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap();
|
||||||
|
let participant3 = roomjid.clone().with_resource(String::from("nick3"));
|
||||||
|
|
||||||
|
let mut room = Room::new(roomjid.clone());
|
||||||
|
|
||||||
|
room.occupants = BTreeMap::new();
|
||||||
|
room.occupants.insert(
|
||||||
|
participant1.resource.clone(), Occupant::new(&room, realjid1.clone(), String::from("nick1")),
|
||||||
|
);
|
||||||
|
room.occupants.insert(
|
||||||
|
participant2.resource.clone(), Occupant::new(&room, realjid2.clone(), String::from("nick2")),
|
||||||
|
);
|
||||||
|
let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3"));
|
||||||
|
room.occupants.insert(participant3.resource.clone(), occupant3.clone());
|
||||||
|
|
||||||
|
// BroadcastPresence::Leave
|
||||||
|
let mut component = TestComponent::new(vec![]);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid1.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid2.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
.with_from(participant3.clone())
|
||||||
|
.with_to(realjid3.clone())
|
||||||
|
.with_payloads(vec![
|
||||||
|
MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||||
|
}.into()
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
room.broadcast_presence(&mut component, &occupant3, &realjid3, BroadcastPresence::Leave).await.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
64
src/tests/iq.rs
Normal file
64
src/tests/iq.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
//
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::component::TestComponent;
|
||||||
|
use crate::handlers::handle_stanza;
|
||||||
|
use crate::room::Room;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use xmpp_parsers::{
|
||||||
|
iq::{Iq, IqType},
|
||||||
|
stanza_error::{DefinedCondition, ErrorType, StanzaError},
|
||||||
|
BareJid, Element, FullJid, Jid,
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref COMPONENT_JID: BareJid = BareJid::from_str("muc.component").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_iq_unimplemented() {
|
||||||
|
let from = Jid::Full(FullJid::from_str("foo@bar/qxx").unwrap());
|
||||||
|
let to = Jid::Bare(COMPONENT_JID.clone());
|
||||||
|
|
||||||
|
let disco: Element = Iq {
|
||||||
|
from: Some(from.clone()),
|
||||||
|
to: Some(to.clone()),
|
||||||
|
id: String::from("disco"),
|
||||||
|
payload: IqType::Get(Element::builder("x", "urn:example:unimplemented").build()),
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let reply: Element = Iq::from_error(
|
||||||
|
"disco",
|
||||||
|
StanzaError::new(
|
||||||
|
ErrorType::Cancel,
|
||||||
|
DefinedCondition::ServiceUnavailable,
|
||||||
|
"en",
|
||||||
|
"No handler defined for this kind of iq.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.with_from(to)
|
||||||
|
.with_to(from)
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut component = TestComponent::new(vec![disco]);
|
||||||
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
|
component.expect(reply);
|
||||||
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
|
}
|
20
src/tests/mod.rs
Normal file
20
src/tests/mod.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod iq;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod presence;
|
|
@ -23,7 +23,6 @@ use std::str::FromStr;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use xmpp_parsers::{
|
use xmpp_parsers::{
|
||||||
delay::Delay,
|
delay::Delay,
|
||||||
iq::{Iq, IqType},
|
|
||||||
message::{Message, MessageType, Subject as MessageSubject},
|
message::{Message, MessageType, Subject as MessageSubject},
|
||||||
muc::{
|
muc::{
|
||||||
user::{Affiliation, Item as MucItem, Role, Status as MucStatus},
|
user::{Affiliation, Item as MucItem, Role, Status as MucStatus},
|
||||||
|
@ -38,40 +37,6 @@ lazy_static! {
|
||||||
static ref COMPONENT_JID: BareJid = BareJid::from_str("muc.component").unwrap();
|
static ref COMPONENT_JID: BareJid = BareJid::from_str("muc.component").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_iq_unimplemented() {
|
|
||||||
let from = Jid::Full(FullJid::from_str("foo@bar/qxx").unwrap());
|
|
||||||
let to = Jid::Bare(COMPONENT_JID.clone());
|
|
||||||
|
|
||||||
let disco: Element = Iq {
|
|
||||||
from: Some(from.clone()),
|
|
||||||
to: Some(to.clone()),
|
|
||||||
id: String::from("disco"),
|
|
||||||
payload: IqType::Get(Element::builder("x", "urn:example:unimplemented").build()),
|
|
||||||
}
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let reply: Element = Iq::from_error(
|
|
||||||
"disco",
|
|
||||||
StanzaError::new(
|
|
||||||
ErrorType::Cancel,
|
|
||||||
DefinedCondition::ServiceUnavailable,
|
|
||||||
"en",
|
|
||||||
"No handler defined for this kind of iq.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.with_from(to)
|
|
||||||
.with_to(from)
|
|
||||||
.into();
|
|
||||||
|
|
||||||
let mut component = TestComponent::new(vec![disco]);
|
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
|
||||||
|
|
||||||
component.expect(reply);
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
|
||||||
component.assert();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_join_presence_empty_room() {
|
async fn test_join_presence_empty_room() {
|
||||||
let from = FullJid::from_str("foo@bar/qxx").unwrap();
|
let from = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
@ -101,7 +66,11 @@ async fn test_join_presence_empty_room() {
|
||||||
.with_to(Jid::Full(from.clone()))
|
.with_to(Jid::Full(from.clone()))
|
||||||
.with_payloads(vec![MucUser {
|
.with_payloads(vec![MucUser {
|
||||||
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
items: vec![{
|
||||||
|
let mut item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
item.jid = Some(from.clone());
|
||||||
|
item
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
.into()]),
|
.into()]),
|
||||||
);
|
);
|
||||||
|
@ -134,10 +103,9 @@ async fn test_join_presence_empty_room() {
|
||||||
String::from(&expected),
|
String::from(&expected),
|
||||||
String::from(&Into::<Element>::into(out))
|
String::from(&Into::<Element>::into(out))
|
||||||
);
|
);
|
||||||
});
|
}, "Room subject to participant1");
|
||||||
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
component.assert();
|
|
||||||
|
|
||||||
assert_eq!(rooms.len(), 1);
|
assert_eq!(rooms.len(), 1);
|
||||||
match rooms.get(&roomjid) {
|
match rooms.get(&roomjid) {
|
||||||
|
@ -174,10 +142,8 @@ async fn test_join_presence_nick_already_assigned() {
|
||||||
let mut component = TestComponent::new(vec![join1, join2]);
|
let mut component = TestComponent::new(vec![join1, join2]);
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
// Ignore self-presence for first participant
|
component.expect_presence(|_| (), "Self-presence for first participant");
|
||||||
component.expect_presence(|_| ());
|
component.expect_message(|_| (), "Subject for first participant");
|
||||||
// Ignore message subject for first participant
|
|
||||||
component.expect_message(|_| ());
|
|
||||||
|
|
||||||
component.expect(
|
component.expect(
|
||||||
Presence::new(PresenceType::Error)
|
Presence::new(PresenceType::Error)
|
||||||
|
@ -193,7 +159,6 @@ async fn test_join_presence_nick_already_assigned() {
|
||||||
);
|
);
|
||||||
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
component.assert();
|
|
||||||
|
|
||||||
match rooms.get(&roomjid) {
|
match rooms.get(&roomjid) {
|
||||||
Some(room) => assert_eq!(room.occupants.len(), 1),
|
Some(room) => assert_eq!(room.occupants.len(), 1),
|
||||||
|
@ -224,10 +189,8 @@ async fn test_join_presence_existing_room() {
|
||||||
let mut component = TestComponent::new(vec![join1, join2]);
|
let mut component = TestComponent::new(vec![join1, join2]);
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
// Ignore self-presence for first participant
|
component.expect_presence(|_| (), "Self-presence for participant1");
|
||||||
component.expect_presence(|_| ());
|
component.expect_message(|_| (), "Subject for participant1");
|
||||||
// Ignore message subject for first participant
|
|
||||||
component.expect_message(|_| ());
|
|
||||||
|
|
||||||
// Participant1 presence for participant2
|
// Participant1 presence for participant2
|
||||||
component.expect(
|
component.expect(
|
||||||
|
@ -260,12 +223,15 @@ async fn test_join_presence_existing_room() {
|
||||||
.with_to(Jid::Full(realjid2.clone()))
|
.with_to(Jid::Full(realjid2.clone()))
|
||||||
.with_payloads(vec![MucUser {
|
.with_payloads(vec![MucUser {
|
||||||
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
items: vec![{
|
||||||
|
let mut item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
item.jid = Some(realjid2.clone());
|
||||||
|
item
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
.into()]),
|
.into()]),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Subject for participant2
|
|
||||||
component.expect_message(|el| {
|
component.expect_message(|el| {
|
||||||
let mut subjects = BTreeMap::new();
|
let mut subjects = BTreeMap::new();
|
||||||
subjects.insert(String::from("en"), MessageSubject::from_str("").unwrap());
|
subjects.insert(String::from("en"), MessageSubject::from_str("").unwrap());
|
||||||
|
@ -292,10 +258,9 @@ async fn test_join_presence_existing_room() {
|
||||||
String::from(&expected),
|
String::from(&expected),
|
||||||
String::from(&Into::<Element>::into(out))
|
String::from(&Into::<Element>::into(out))
|
||||||
);
|
);
|
||||||
});
|
}, "Subject for participant2");
|
||||||
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
component.assert();
|
|
||||||
|
|
||||||
match rooms.get(&roomjid) {
|
match rooms.get(&roomjid) {
|
||||||
Some(room) => assert_eq!(room.occupants.len(), 2),
|
Some(room) => assert_eq!(room.occupants.len(), 2),
|
||||||
|
@ -303,6 +268,73 @@ async fn test_join_presence_existing_room() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_presence_resync() {
|
||||||
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
let realjid2 = FullJid::from_str("qxx@bar/foo").unwrap();
|
||||||
|
let roomjid = COMPONENT_JID.clone().with_node("room");
|
||||||
|
let participant1 = roomjid.clone().with_resource("nick1");
|
||||||
|
let participant2 = roomjid.clone().with_resource("nick2");
|
||||||
|
|
||||||
|
let join1: Element = Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(realjid1.clone()))
|
||||||
|
.with_to(Jid::Full(participant1.clone()))
|
||||||
|
.with_payloads(vec![Muc::new().into()])
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let join2: Element = Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(realjid2.clone()))
|
||||||
|
.with_to(Jid::Full(participant2.clone()))
|
||||||
|
.with_payloads(vec![Muc::new().into()])
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut component = TestComponent::new(vec![join1.clone(), join2, join1]);
|
||||||
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
|
component.expect_presence(|_| (), "Self-presence for participant1");
|
||||||
|
component.expect_message(|_| (), "Subject for participant1");
|
||||||
|
component.expect_presence(|_| (), "Participant1 presence for participant2");
|
||||||
|
component.expect_presence(|_| (), "Participant2 presence for participant1");
|
||||||
|
component.expect_presence(|_| (), "Self-presence for participant2");
|
||||||
|
component.expect_message(|_| (), "Subject for participant2");
|
||||||
|
|
||||||
|
// Resync: Participant2 presence for participant1
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(participant2.clone()))
|
||||||
|
.with_to(Jid::Full(realjid1.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: Vec::new(),
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)],
|
||||||
|
}
|
||||||
|
.into()]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resync: Participant1 self-presence
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(participant1))
|
||||||
|
.with_to(Jid::Full(realjid1.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
|
items: vec![{
|
||||||
|
let mut item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
item.jid = Some(realjid1.clone());
|
||||||
|
item
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
.into()]),
|
||||||
|
);
|
||||||
|
|
||||||
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
|
|
||||||
|
match rooms.get(&roomjid) {
|
||||||
|
Some(room) => assert_eq!(room.occupants.len(), 2),
|
||||||
|
None => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_leave_non_existing_room() {
|
async fn test_leave_non_existing_room() {
|
||||||
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
let realjid1 = FullJid::from_str("foo@bar/qxx").unwrap();
|
||||||
|
@ -319,7 +351,6 @@ async fn test_leave_non_existing_room() {
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
// The leave should be ignored, there should be no output at all.
|
// The leave should be ignored, there should be no output at all.
|
||||||
component.assert();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -343,10 +374,8 @@ async fn test_leave_last_participant() {
|
||||||
let mut component = TestComponent::new(vec![join1, leave1]);
|
let mut component = TestComponent::new(vec![join1, leave1]);
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
// Ignore self-presence for participant1
|
component.expect_presence(|_| (), "Self-presence for participant1");
|
||||||
component.expect_presence(|_| ());
|
component.expect_message(|_| (), "Subject for participant1");
|
||||||
// Ignore subject message for participant1
|
|
||||||
component.expect_message(|_| ());
|
|
||||||
|
|
||||||
component.expect(
|
component.expect(
|
||||||
Presence::new(PresenceType::Unavailable)
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
@ -361,7 +390,6 @@ async fn test_leave_last_participant() {
|
||||||
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
|
|
||||||
component.assert();
|
|
||||||
assert_eq!(rooms.len(), 0);
|
assert_eq!(rooms.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,29 +422,12 @@ async fn test_leave_room_not_last() {
|
||||||
let mut component = TestComponent::new(vec![join1, join2, leave2]);
|
let mut component = TestComponent::new(vec![join1, join2, leave2]);
|
||||||
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
// Ignore self-presence for participant1
|
component.expect_presence(|_| (), "Self-presence for participant1");
|
||||||
component.expect_presence(|_| ());
|
component.expect_message(|_| (), "Subject message for participant1");
|
||||||
// Ignore subject message for participant1
|
component.expect_presence(|_| (), "Participant1 presence for participant2");
|
||||||
component.expect_message(|_| ());
|
component.expect_presence(|_| (), "Self-presence for participant2");
|
||||||
// Ignore participant1 presence for participant2
|
component.expect_presence(|_| (), "Participant2 presence for participant1");
|
||||||
component.expect_presence(|_| ());
|
component.expect_message(|_| (), "Subject message for participant2");
|
||||||
// Ignore self-presence for participant2
|
|
||||||
component.expect_presence(|_| ());
|
|
||||||
// Ignore participant2 presence for participant1
|
|
||||||
component.expect_presence(|_| ());
|
|
||||||
// Ignore subject message for participant2
|
|
||||||
component.expect_message(|_| ());
|
|
||||||
|
|
||||||
component.expect(
|
|
||||||
Presence::new(PresenceType::Unavailable)
|
|
||||||
.with_from(Jid::Full(participant2.clone()))
|
|
||||||
.with_to(Jid::Full(realjid2.clone()))
|
|
||||||
.with_payloads(vec![MucUser {
|
|
||||||
status: vec![MucStatus::SelfPresence],
|
|
||||||
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
|
||||||
}
|
|
||||||
.into()]),
|
|
||||||
);
|
|
||||||
|
|
||||||
component.expect(
|
component.expect(
|
||||||
Presence::new(PresenceType::Unavailable)
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
@ -429,11 +440,111 @@ async fn test_leave_room_not_last() {
|
||||||
.into()]),
|
.into()]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::Unavailable)
|
||||||
|
.with_from(Jid::Full(participant2.clone()))
|
||||||
|
.with_to(Jid::Full(realjid2.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence],
|
||||||
|
items: vec![MucItem::new(Affiliation::Owner, Role::None)],
|
||||||
|
}
|
||||||
|
.into()]),
|
||||||
|
);
|
||||||
|
|
||||||
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
component.assert();
|
|
||||||
assert_eq!(rooms.len(), 1);
|
assert_eq!(rooms.len(), 1);
|
||||||
match rooms.get(&roomjid) {
|
match rooms.get(&roomjid) {
|
||||||
Some(room) => assert_eq!(room.occupants.len(), 1),
|
Some(room) => assert_eq!(room.occupants.len(), 1),
|
||||||
None => panic!(),
|
None => panic!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_join_msn() {
|
||||||
|
let barejid = BareJid::from_str("foo@bar").unwrap();
|
||||||
|
let realjid1 = barejid.clone().with_resource("qxx");
|
||||||
|
let realjid2 = barejid.clone().with_resource("hah");
|
||||||
|
let roomjid = COMPONENT_JID.clone().with_node("room");
|
||||||
|
let participant1 = roomjid.clone().with_resource("nick1");
|
||||||
|
|
||||||
|
let join1: Element = Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(realjid1.clone()))
|
||||||
|
.with_to(Jid::Full(participant1.clone()))
|
||||||
|
.with_payloads(vec![Muc::new().into()])
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let join2: Element = Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(realjid2.clone()))
|
||||||
|
.with_to(Jid::Full(participant1.clone()))
|
||||||
|
.with_payloads(vec![Muc::new().into()])
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let mut component = TestComponent::new(vec![join1, join2]);
|
||||||
|
let mut rooms: HashMap<BareJid, Room> = HashMap::new();
|
||||||
|
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(participant1.clone()))
|
||||||
|
.with_to(Jid::Full(realjid1.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
|
items: vec![{
|
||||||
|
let mut item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
item.jid = Some(realjid1.clone());
|
||||||
|
item
|
||||||
|
}],
|
||||||
|
}.into()])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect_message(|_| (), "Subject message for participant1");
|
||||||
|
|
||||||
|
// New session joins
|
||||||
|
// Participant1 presence for participant2
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(participant1.clone()))
|
||||||
|
.with_to(Jid::Full(realjid1.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: Vec::new(),
|
||||||
|
items: {
|
||||||
|
let item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
let mut item1 = item.clone();
|
||||||
|
item1.jid = Some(realjid1.clone());
|
||||||
|
let mut item2 = item.clone();
|
||||||
|
item2.jid = Some(realjid2.clone());
|
||||||
|
vec![item1, item2]
|
||||||
|
},
|
||||||
|
}.into()])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Self-presence for participant2
|
||||||
|
component.expect(
|
||||||
|
Presence::new(PresenceType::None)
|
||||||
|
.with_from(Jid::Full(participant1.clone()))
|
||||||
|
.with_to(Jid::Full(realjid2.clone()))
|
||||||
|
.with_payloads(vec![MucUser {
|
||||||
|
status: vec![MucStatus::SelfPresence, MucStatus::AssignedNick],
|
||||||
|
items: {
|
||||||
|
let item = MucItem::new(Affiliation::Owner, Role::Moderator);
|
||||||
|
let mut item1 = item.clone();
|
||||||
|
item1.jid = Some(realjid1.clone());
|
||||||
|
let mut item2 = item.clone();
|
||||||
|
item2.jid = Some(realjid2.clone());
|
||||||
|
vec![item1, item2]
|
||||||
|
},
|
||||||
|
}.into()])
|
||||||
|
);
|
||||||
|
|
||||||
|
component.expect_message(|_| (), "Subject message for participant2");
|
||||||
|
|
||||||
|
handle_stanza(&mut component, &mut rooms).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(rooms.len(), 1);
|
||||||
|
match rooms.get(&roomjid) {
|
||||||
|
Some(room) => {
|
||||||
|
assert_eq!(room.occupants.len(), 1);
|
||||||
|
assert_eq!(room.occupants.get(&participant1.resource).unwrap().sessions.len(), 2);
|
||||||
|
},
|
||||||
|
None => panic!(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue