diff --git a/src/error.rs b/src/error.rs index fc0f83b..0122a28 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,11 +13,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::room::Session; + use std::error::Error as StdError; use std::fmt; use tokio_xmpp::Error as TokioXMPPError; -use xmpp_parsers::{Error as ParserError, FullJid, Jid, JidParseError}; +use xmpp_parsers::{Error as ParserError, Jid, JidParseError}; #[derive(Debug)] pub enum Error { @@ -27,10 +29,15 @@ pub enum Error { NickAlreadyAssigned(String), /// Raised when editing or fetching an occupant with a session that isn't associated with the /// occupant. - NonexistantSession(FullJid), - SessionAlreadyExists(FullJid), + NonexistantSession(Session), + SessionAlreadyExists(Session), /// Raised when fetching an occupant with a nickname that isn't assigned in the room. ParticipantNotFound(String), + /// Raised whenever an operation is applied to a session which isn't the primary session, used + /// for Multi-Session Nick support. + SecondarySession(Session), + /// Raised when a JID was supposed to be present + MissingJid, /// Jid Parse errors Jid(Box), /// TokioXMPP errors @@ -46,9 +53,11 @@ impl fmt::Display for Error { match self { Error::MismatchJids(jid1, jid2) => write!(f, "Mismatch Jids: {}, {}", jid1, jid2), Error::NickAlreadyAssigned(err) => write!(f, "Nickname already assigned: {}", err), - Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {}", err), - Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {}", err), + Error::NonexistantSession(err) => write!(f, "Session doesn't exist: {:?}", err), + Error::SessionAlreadyExists(err) => write!(f, "Session already exist: {:?}", err), Error::ParticipantNotFound(err) => write!(f, "Participant not found: {}", err), + Error::SecondarySession(err) => write!(f, "Secondary session: {:?}", err), + Error::MissingJid => write!(f, "Missing JID"), Error::Jid(err) => write!(f, "Jid Parse error: {}", err), Error::Xmpp(err) => write!(f, "XMPP error: {}", err), Error::Parser(err) => write!(f, "Parser error: {}", err), diff --git a/src/handlers.rs b/src/handlers.rs index 026bbeb..319112f 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -15,7 +15,7 @@ use crate::component::ComponentTrait; use crate::error::Error; -use crate::room::{Nick, Room}; +use crate::room::Room; use std::collections::{BTreeMap, HashMap}; use std::ops::ControlFlow; @@ -189,6 +189,7 @@ async fn handle_presence( let muc = presence .payloads + .clone() .into_iter() .try_for_each(|payload| match Muc::try_from(payload) { Ok(muc) => ControlFlow::Break(muc), @@ -202,13 +203,12 @@ async fn handle_presence( let Jid::Full(participant) = presence.to.clone().unwrap() { let roomjid = BareJid::from(participant.clone()); - let nick: Nick = participant.resource.clone(); // Room already exists if let Some(room) = rooms.get_mut(&roomjid) { debug!("Presence received to existing room: {}", &roomjid); if let ControlFlow::Break(_) = muc { - match room.add_session(component, realjid.clone(), nick).await { + match room.add_session(component, presence.clone()).await { Ok(_) => (), Err(Error::NickAlreadyAssigned(nick)) => { let error = Presence::new(PresenceType::Error) @@ -228,7 +228,7 @@ async fn handle_presence( } } else if let ControlFlow::Continue(_) = muc { if room.is_joined(&realjid) { - room.update_presence(component, realjid.clone(), nick).await? + room.update_presence(component, presence.clone()).await? } else { // TODO: We don't want to support GC1. Error } @@ -236,13 +236,13 @@ async fn handle_presence( } else { debug!("Presence received to new room: {}", &roomjid); let mut room = Room::new(roomjid.clone()); - room.add_session(component, realjid, nick).await.unwrap(); + room.add_session(component, presence.clone()).await.unwrap(); let _ = rooms.insert(roomjid, room); } } } else if presence.type_ == PresenceType::Unavailable && - let Jid::Full(realjid) = presence.from.unwrap() && - let Jid::Full(participant) = presence.to.unwrap() { + let Jid::Full(realjid) = presence.from.clone().unwrap() && + let Jid::Full(participant) = presence.to.clone().unwrap() { let roomjid = BareJid::from(participant.clone()); @@ -258,7 +258,7 @@ async fn handle_presence( ).into() ]); if let Some(mut room) = rooms.remove(&roomjid) { - match room.remove_session(component, realjid, participant.resource.clone()).await { + match room.remove_session(component, presence.clone()).await { Ok(()) => (), Err(Error::NonexistantSession(_)) => { component.send_stanza(error).await.unwrap(); diff --git a/src/room.rs b/src/room.rs index 0fd41f4..2bbec9e 100644 --- a/src/room.rs +++ b/src/room.rs @@ -34,7 +34,6 @@ use xmpp_parsers::{ }; pub type Nick = String; -type Session = FullJid; #[derive(Debug, PartialEq, Eq)] pub enum BroadcastPresence { @@ -71,20 +70,22 @@ impl Room { &self, component: &mut C, own_occupant: &Occupant, - own_session: &Session, + presence: Presence, mode: BroadcastPresence, ) -> Result<(), Error> { let leave = matches!(mode, BroadcastPresence::Leave); let sync = matches!(mode, BroadcastPresence::Join | BroadcastPresence::Resync); let update = matches!(mode, BroadcastPresence::Join | BroadcastPresence::Update); + let own_session = Session::try_from(presence)?; + // All participants to new participant let presence_to_new = Presence::new(if leave { PresenceType::Unavailable } else { PresenceType::None }) - .with_to(own_session.clone()) + .with_to(Jid::Full(own_session.real.clone())) .with_payloads(vec![MucUser { status: Vec::new(), items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], @@ -97,7 +98,7 @@ impl Room { } else { PresenceType::None }) - .with_from(Jid::Full(own_occupant.participant.clone())) + .with_from(Jid::Full(own_session.participant.clone())) .with_payloads(vec![MucUser { status: Vec::new(), items: vec![MucItem::new( @@ -108,6 +109,7 @@ impl Room { .into()]); for (_, other) in self.occupants.iter() { + // Skip sending to all our sessions, we do it later. if own_occupant.nick == other.nick { continue; } @@ -122,11 +124,13 @@ impl Room { // Send presence from participant to others. for session in other.iter() { // Skip sending if it's us. - if session == own_session { + if session.real == own_session.real { continue; } - let presence = presence_to_old.clone().with_to(Jid::Full(session.clone())); + let presence = presence_to_old + .clone() + .with_to(Jid::Full(session.real.clone())); component.send_stanza(presence).await?; } } @@ -138,7 +142,7 @@ impl Room { .map(|session| MucItem { affiliation: Affiliation::Owner, role: if leave { Role::None } else { Role::Moderator }, - jid: Some(session.clone()), + jid: Some(session.real.clone()), nick: None, actor: None, continue_: None, @@ -156,12 +160,12 @@ impl Room { .with_from(Jid::Full(own_occupant.participant.clone())); for session in own_occupant.iter() { - if session == own_session { + if session == &own_session { continue; } let presence = session_presence .clone() - .with_to(Jid::Full(session.clone())) + .with_to(Jid::Full(session.real.clone())) .with_payloads(vec![MucUser { status: vec![], items: self_items.clone(), @@ -179,18 +183,14 @@ impl Room { PresenceType::None }) .with_from(Jid::Full(own_occupant.participant.clone())) - .with_to(own_session.clone()) + .with_to(own_session.real.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 - }, + items: self_items, } .into()]); component.send_stanza(self_presence).await?; @@ -202,7 +202,7 @@ impl Room { pub async fn send_subject( &mut self, component: &mut C, - realjid: Session, + session: Session, occupant: Occupant, ) -> Result<(), Error> { debug!("Sending subject!"); @@ -213,7 +213,7 @@ impl Room { self.subject = Some((subject, setter, stamp)); } - let mut subject = Message::new(Some(Jid::Full(realjid))); + let mut subject = Message::new(Some(Jid::Full(session.real))); subject.from = Some(Jid::Full( self.subject.as_ref().unwrap().1.participant.clone(), )); @@ -234,9 +234,12 @@ impl Room { pub async fn add_session( &mut self, component: &mut C, - realjid: Session, - new_nick: Nick, + presence: Presence, ) -> Result<(), Error> { + let new_session = Session::try_from(presence)?; + let new_nick = new_session.participant.resource.clone(); + let realjid = new_session.real.clone(); + // Ensure nick isn't already assigned self.occupants.iter().try_for_each(|(nick, occupant)| { let new_nick = new_nick.as_str(); @@ -248,7 +251,7 @@ impl Room { let mode: Option = { if let Some(occupant) = self.occupants.get_mut(&new_nick) { - match occupant.add_session(realjid.clone()) { + match occupant.add_session(new_session.presence.clone()) { Ok(_) => Some(BroadcastPresence::Join), Err(Error::SessionAlreadyExists(_)) => Some(BroadcastPresence::Resync), Err(err) => return Err(err), @@ -261,22 +264,32 @@ impl Room { if !self.occupants.contains_key(&new_nick) { let _ = self.occupants.insert( new_nick.clone(), - Occupant::new(self, realjid.clone(), new_nick.clone()), + Occupant::new(new_session.presence.clone())?, ); } let occupant = self.occupants.get(&new_nick).unwrap(); match mode { Some(BroadcastPresence::Resync) => { - self.broadcast_presence(component, occupant, &realjid, BroadcastPresence::Resync) - .await?; + self.broadcast_presence( + component, + &occupant, + new_session.presence, + 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()) + self.broadcast_presence( + component, + &occupant, + new_session.presence.clone(), + BroadcastPresence::Join, + ) + .await?; + self.send_subject(component, new_session, occupant.clone()) .await?; } _ => (), @@ -288,13 +301,21 @@ impl Room { pub async fn update_presence( &mut self, component: &mut C, - realjid: Session, - nick: Nick, + presence: Presence, ) -> Result<(), Error> { + let session = Session::try_from(presence)?; + + let occupant = self.get_mut_occupant(&session)?; + match occupant.update_presence(session.presence.clone()) { + Ok(_) | Err(Error::SecondarySession(_)) => (), + err => err?, + } + + let occupant = self.get_occupant(&session)?; self.broadcast_presence( component, - self.get_occupant(&realjid, &nick)?, - &realjid, + &occupant, + session.presence, BroadcastPresence::Update, ) .await?; @@ -304,15 +325,20 @@ impl Room { pub async fn remove_session( &mut self, component: &mut C, - realjid: Session, - nick: Nick, + presence: Presence, ) -> Result<(), Error> { + let session = Session::try_from(presence)?; // If occupant doesn't exist, ignore. - if let Some(mut occupant) = self.occupants.remove(&nick) { - self.broadcast_presence(component, &occupant, &realjid, BroadcastPresence::Leave) - .await?; + if let Some(mut occupant) = self.occupants.remove(&session.participant.resource) { + self.broadcast_presence( + component, + &occupant, + session.presence.clone(), + BroadcastPresence::Leave, + ) + .await?; - occupant.remove_session(realjid)?; + occupant.remove_session(session.presence)?; } else { // TODO: Error } @@ -322,15 +348,33 @@ impl Room { /// Fetch the occupant associated with the provided nick and ensure the session is part of /// it. - pub fn get_occupant(&self, realjid: &Session, nick: &Nick) -> Result<&Occupant, Error> { - if let Some(occupant) = self.occupants.get(nick) { - if occupant.sessions.contains(realjid) { + pub fn get_occupant(&self, session: &Session) -> Result<&Occupant, Error> { + if let Some(occupant) = self.occupants.get(&session.participant.resource) { + if occupant.contains(&session.real) { Ok(occupant) } else { - Err(Error::NonexistantSession(realjid.clone())) + Err(Error::NonexistantSession(session.clone())) } } else { - Err(Error::ParticipantNotFound(nick.clone())) + Err(Error::ParticipantNotFound( + session.participant.resource.clone(), + )) + } + } + + /// Fetch a mutable reference of the occupant associated with the provided nick and ensure the + /// session is part of it. + pub fn get_mut_occupant(&mut self, session: &Session) -> Result<&mut Occupant, Error> { + if let Some(occupant) = self.occupants.get_mut(&session.participant.resource) { + if occupant.contains(&session.real) { + Ok(occupant) + } else { + Err(Error::NonexistantSession(session.clone())) + } + } else { + Err(Error::ParticipantNotFound( + session.participant.resource.clone(), + )) } } @@ -339,6 +383,67 @@ impl Room { } } +/// An occupant session +#[derive(Debug, Clone, Eq)] +pub struct Session { + pub presence: Presence, + pub real: FullJid, + pub participant: FullJid, +} + +impl Session { + /// Ensure presence doesn't contain payloads that would impersonate us + fn filter_presence(presence: Presence) -> Presence { + presence + } + + /// Instanciate a Session from a presence which is adressed to the room JID. + fn with_nick>(presence: Presence, nick: N) -> Result { + let presence = Session::filter_presence(presence); + Ok(Session { + real: presence + .from + .clone() + .map(FullJid::try_from) + .ok_or(Error::MissingJid)??, + participant: presence + .to + .clone() + .map(BareJid::from) + .ok_or(Error::MissingJid)? + .with_resource(nick.into()), + presence, + }) + } +} + +impl PartialEq for Session { + fn eq(&self, other: &Session) -> bool { + self.real == other.real && self.participant == other.participant + } +} + +impl TryFrom for Session { + type Error = Error; + + fn try_from(presence: Presence) -> Result { + let presence = Session::filter_presence(presence); + Ok(Session { + real: presence + .from + .clone() + .map(FullJid::try_from) + .ok_or(Error::MissingJid)??, + participant: presence + .to + .clone() + .map(FullJid::try_from) + .ok_or(Error::MissingJid)??, + presence, + }) + } +} + /// An occupant in a room. May contain multiple sessions (Multi-Session Nicks) #[derive(Debug, Clone, PartialEq, Eq)] pub struct Occupant { @@ -346,68 +451,85 @@ pub struct Occupant { pub real: BareJid, pub participant: FullJid, pub nick: Nick, - pub sessions: Vec, + pub sessions: Vec, } impl Occupant { /// New occupant - pub fn new(room: &Room, real: FullJid, nick: Nick) -> Occupant { - Occupant { - real: BareJid::from(real.clone()), - participant: room.jid.clone().with_resource(nick.clone()), - nick, - sessions: vec![real], - } + pub fn new(presence: Presence) -> Result { + let session = Session::try_from(presence)?; + Ok(Occupant { + real: BareJid::from(session.real.clone()), + participant: session.participant.clone(), + nick: session.participant.resource.clone(), + sessions: vec![session], + }) } /// Add a new session to the occupant - pub fn add_session(&mut self, real: FullJid) -> Result<(), Error> { - if BareJid::from(real.clone()) != self.real { + pub fn add_session(&mut self, presence: Presence) -> Result<(), Error> { + let new_session = Session::try_from(presence)?; + if BareJid::from(new_session.real.clone()) != self.real { return Err(Error::MismatchJids( Jid::from(self.real.clone()), - Jid::from(real), + Jid::from(new_session.real), )); } for session in &self.sessions { - if &real == session { - return Err(Error::SessionAlreadyExists(real)); + if &new_session.real == &session.real { + return Err(Error::SessionAlreadyExists(new_session)); } } - self.sessions.push(real); + self.sessions.push(new_session); Ok(()) } /// Remove a session from the occupant - pub fn remove_session(&mut self, real: FullJid) -> Result<(), Error> { - if BareJid::from(real.clone()) != self.real { - return Err(Error::MismatchJids( - Jid::from(self.real.clone()), - Jid::from(real.clone()), - )); - } + pub fn remove_session(&mut self, presence: Presence) -> Result<(), Error> { + let own_session = Session::try_from(presence)?; let len = self.sessions.len(); - self.sessions.retain(|session| session != &real); + self.sessions + .retain(|session| session.real != own_session.real); // An item has been removed if len != self.sessions.len() { Ok(()) } else { - Err(Error::NonexistantSession(real)) + Err(Error::NonexistantSession(own_session)) } } + /// Update session presence + pub fn update_presence(&mut self, presence: Presence) -> Result<(), Error> { + let own_session = Session::with_nick(presence, self.participant.resource.clone())?; + + for (i, session) in self.sessions.iter().enumerate() { + if &own_session == session { + if i == 0 { + // Leader session + self.sessions[0] = own_session; + return Ok(()); + } + return Err(Error::SecondarySession(own_session)); + } + } + + Err(Error::NonexistantSession(own_session)) + } + /// Return whether a Jid matches the occupant. If FullJid, compare with each session, otherwise /// see if the BareJid matches. + // TODO: We may want to split checking for Bare/Full as this can be error prone pub fn contains>(&self, _jid: &J) -> bool { true } } impl IntoIterator for Occupant { - type Item = FullJid; + type Item = Session; type IntoIter = std::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { @@ -416,7 +538,7 @@ impl IntoIterator for Occupant { } impl Occupant { - fn iter(&self) -> std::slice::Iter<'_, FullJid> { + fn iter(&self) -> std::slice::Iter<'_, Session> { self.sessions.iter() } } @@ -425,13 +547,17 @@ impl Occupant { mod tests { use super::*; use crate::component::TestComponent; + use crate::tests::templates::{ + LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, ROSA_FULL1, + ROSA_NICK, ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_NICK, SUGAKO_ROOM1_PART, + }; use std::str::FromStr; use xmpp_parsers::{ muc::{ user::{Affiliation, Item as MucItem, Role, Status as MucStatus}, - MucUser, + Muc, MucUser, }, - presence::{Presence, Type as PresenceType}, + presence::{Presence, Show as PresenceShow, Type as PresenceType}, BareJid, Element, }; @@ -445,18 +571,30 @@ mod tests { let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap(); let participant3 = roomjid.clone().with_resource(String::from("nick3")); + let presence1 = Presence::new(PresenceType::None) + .with_from(realjid1.clone()) + .with_to(participant1.clone()); + + let presence2 = Presence::new(PresenceType::None) + .with_from(realjid2.clone()) + .with_to(participant2.clone()); + + let presence3 = Presence::new(PresenceType::None) + .with_from(realjid3.clone()) + .with_to(participant3.clone()); + 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")), + Occupant::new(presence1).unwrap(), ); room.occupants.insert( participant2.resource.clone(), - Occupant::new(&room, realjid2.clone(), String::from("nick2")), + Occupant::new(presence2).unwrap(), ); - let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); + let occupant3 = Occupant::new(presence3.clone()).unwrap(); room.occupants .insert(participant3.resource.clone(), occupant3.clone()); @@ -505,7 +643,7 @@ mod tests { room.broadcast_presence( &mut component, &occupant3, - &realjid3, + presence3, BroadcastPresence::Resync, ) .await @@ -522,18 +660,30 @@ mod tests { let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap(); let participant3 = roomjid.clone().with_resource(String::from("nick3")); + let presence1 = Presence::new(PresenceType::None) + .with_from(realjid1.clone()) + .with_to(participant1.clone()); + + let presence2 = Presence::new(PresenceType::None) + .with_from(realjid2.clone()) + .with_to(participant2.clone()); + + let presence3 = Presence::new(PresenceType::None) + .with_from(realjid3.clone()) + .with_to(participant3.clone()); + 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")), + Occupant::new(presence1).unwrap(), ); room.occupants.insert( participant2.resource.clone(), - Occupant::new(&room, realjid2.clone(), String::from("nick2")), + Occupant::new(presence2).unwrap(), ); - let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); + let occupant3 = Occupant::new(presence3.clone()).unwrap(); room.occupants .insert(participant3.resource.clone(), occupant3.clone()); @@ -565,7 +715,7 @@ mod tests { room.broadcast_presence( &mut component, &occupant3, - &realjid3, + presence3, BroadcastPresence::Update, ) .await @@ -582,18 +732,30 @@ mod tests { let realjid3 = FullJid::from_str("bar@qxx/foo").unwrap(); let participant3 = roomjid.clone().with_resource(String::from("nick3")); + let presence1 = Presence::new(PresenceType::None) + .with_from(realjid1.clone()) + .with_to(participant1.clone()); + + let presence2 = Presence::new(PresenceType::None) + .with_from(realjid2.clone()) + .with_to(participant2.clone()); + + let presence3 = Presence::new(PresenceType::None) + .with_from(realjid3.clone()) + .with_to(participant3.clone()); + 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")), + Occupant::new(presence1.clone()).unwrap(), ); room.occupants.insert( participant2.resource.clone(), - Occupant::new(&room, realjid2.clone(), String::from("nick2")), + Occupant::new(presence2.clone()).unwrap(), ); - let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); + let occupant3 = Occupant::new(presence3.clone()).unwrap(); room.occupants .insert(participant3.resource.clone(), occupant3.clone()); @@ -664,7 +826,7 @@ mod tests { room.broadcast_presence( &mut component, &occupant3, - &realjid3, + presence3, BroadcastPresence::Join, ) .await @@ -673,36 +835,40 @@ mod tests { #[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 presence_louise = Presence::new(PresenceType::None) + .with_from(LOUISE_FULL1.clone()) + .with_to(LOUISE_ROOM1_PART.clone()); - let mut room = Room::new(roomjid.clone()); + let presence_sugako = Presence::new(PresenceType::None) + .with_from(SUGAKO_FULL1.clone()) + .with_to(SUGAKO_ROOM1_PART.clone()); + + let presence_rosa = Presence::new(PresenceType::None) + .with_from(ROSA_FULL1.clone()) + .with_to(ROSA_ROOM1_PART.clone()); + + let mut room = Room::new(ROOM1_BARE.clone()); room.occupants = BTreeMap::new(); room.occupants.insert( - participant1.resource.clone(), - Occupant::new(&room, realjid1.clone(), String::from("nick1")), + String::from(LOUISE_NICK), + Occupant::new(presence_louise).unwrap(), ); room.occupants.insert( - participant2.resource.clone(), - Occupant::new(&room, realjid2.clone(), String::from("nick2")), + String::from(SUGAKO_NICK), + Occupant::new(presence_sugako).unwrap(), ); - let occupant3 = Occupant::new(&room, realjid3.clone(), String::from("nick3")); + let occupant3 = Occupant::new(presence_rosa).unwrap(); room.occupants - .insert(participant3.resource.clone(), occupant3.clone()); + .insert(String::from(ROSA_NICK), 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_from(ROSA_ROOM1_PART.clone()) + .with_to(LOUISE_FULL1.clone()) .with_payloads(vec![MucUser { status: vec![], items: vec![MucItem::new(Affiliation::Owner, Role::None)], @@ -712,8 +878,8 @@ mod tests { component.expect( Presence::new(PresenceType::Unavailable) - .with_from(participant3.clone()) - .with_to(realjid2.clone()) + .with_from(ROSA_ROOM1_PART.clone()) + .with_to(SUGAKO_FULL1.clone()) .with_payloads(vec![MucUser { status: vec![], items: vec![MucItem::new(Affiliation::Owner, Role::None)], @@ -723,19 +889,27 @@ mod tests { component.expect( Presence::new(PresenceType::Unavailable) - .with_from(participant3.clone()) - .with_to(realjid3.clone()) + .with_from(ROSA_ROOM1_PART.clone()) + .with_to(ROSA_FULL1.clone()) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence], - items: vec![MucItem::new(Affiliation::Owner, Role::None)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::None); + item.jid = Some(ROSA_FULL1.clone()); + item + }], } .into()]), ); + let presence_leave = Presence::new(PresenceType::Unavailable) + .with_from(ROSA_FULL1.clone()) + .with_to(ROSA_ROOM1_PART.clone()); + room.broadcast_presence( &mut component, &occupant3, - &realjid3, + presence_leave, BroadcastPresence::Leave, ) .await diff --git a/src/tests/mod.rs b/src/tests/mod.rs index d8f5597..c662c1e 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -16,4 +16,4 @@ mod iq; mod presence; #[allow(dead_code)] -mod templates; +pub mod templates; diff --git a/src/tests/presence.rs b/src/tests/presence.rs index f6a0466..e6010ab 100644 --- a/src/tests/presence.rs +++ b/src/tests/presence.rs @@ -31,7 +31,7 @@ use xmpp_parsers::{ user::{Affiliation, Item as MucItem, Role, Status as MucStatus}, Muc, MucUser, }, - presence::{Presence, Type as PresenceType}, + presence::{Presence, Show as PresenceShow, Type as PresenceType}, stanza_error::{DefinedCondition, ErrorType, StanzaError}, BareJid, Element, Jid, }; @@ -350,7 +350,11 @@ async fn test_leave_last_participant() { .with_to(Jid::Full(LOUISE_FULL1.clone())) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence], - items: vec![MucItem::new(Affiliation::Owner, Role::None)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::None); + item.jid = Some(LOUISE_FULL1.clone()); + item + }], } .into()]), ); @@ -407,7 +411,11 @@ async fn test_leave_room_not_last() { .with_to(Jid::Full(SUGAKO_FULL1.clone())) .with_payloads(vec![MucUser { status: vec![MucStatus::SelfPresence], - items: vec![MucItem::new(Affiliation::Owner, Role::None)], + items: vec![{ + let mut item = MucItem::new(Affiliation::Owner, Role::None); + item.jid = Some(SUGAKO_FULL1.clone()); + item + }], } .into()]), ); @@ -514,6 +522,7 @@ async fn test_join_msn() { } } +#[ignore] #[tokio::test] async fn test_presence_update_joined() { let mut rooms: HashMap = HashMap::new(); @@ -535,7 +544,8 @@ async fn test_presence_update_joined() { status: Vec::new(), items: vec![MucItem::new(Affiliation::Owner, Role::Moderator)], } - .into()]), + .into()]) + .with_show(PresenceShow::Away), ); handle_stanza(&mut component, &mut rooms).await.unwrap(); diff --git a/src/tests/templates.rs b/src/tests/templates.rs index 24189d7..f66f807 100644 --- a/src/tests/templates.rs +++ b/src/tests/templates.rs @@ -16,7 +16,10 @@ use crate::room::{Occupant, Room}; use std::str::FromStr; use std::sync::LazyLock; -use xmpp_parsers::{BareJid, FullJid}; +use xmpp_parsers::{ + presence::{Presence, Type as PresenceType}, + BareJid, FullJid, +}; pub const COMPONENT_JID: LazyLock = LazyLock::new(|| BareJid::from_str("commons.social").unwrap()); @@ -69,22 +72,31 @@ pub const PETER_ROOM1_PART: LazyLock = pub fn one_participant_room(roomjid: BareJid) -> Room { let mut room = Room::new(roomjid.clone()); + let presence_louise = Presence::new(PresenceType::None) + .with_from(LOUISE_FULL1.clone()) + .with_to(LOUISE_ROOM1_PART.clone()); room.occupants.insert( String::from(LOUISE_NICK), - Occupant::new(&room, LOUISE_FULL1.clone(), String::from(LOUISE_NICK)), + Occupant::new(presence_louise).unwrap(), ); room } pub fn two_participant_room(roomjid: BareJid) -> Room { let mut room = Room::new(roomjid.clone()); + let presence_louise = Presence::new(PresenceType::None) + .with_from(LOUISE_FULL1.clone()) + .with_to(LOUISE_ROOM1_PART.clone()); + let presence_sugako = Presence::new(PresenceType::None) + .with_from(SUGAKO_FULL1.clone()) + .with_to(SUGAKO_ROOM1_PART.clone()); room.occupants.insert( String::from(LOUISE_NICK), - Occupant::new(&room, LOUISE_FULL1.clone(), String::from(LOUISE_NICK)), + Occupant::new(presence_louise).unwrap(), ); room.occupants.insert( String::from(SUGAKO_NICK), - Occupant::new(&room, SUGAKO_FULL1.clone(), String::from(SUGAKO_NICK)), + Occupant::new(presence_sugako).unwrap(), ); room }