diff --git a/src/occupant.rs b/src/occupant.rs index e2c4dc0..0d22582 100644 --- a/src/occupant.rs +++ b/src/occupant.rs @@ -42,6 +42,12 @@ impl Occupant { }) } + /// Return the primary session used in MSN context + // TODO: Criteria for primary session may change later on. + pub fn get_primary_session(&self) -> &Session { + &self.sessions[0] + } + /// Add a new session to the occupant pub fn add_session(&mut self, presence: Presence) -> Result<(), Error> { let new_session = Session::try_from(presence)?; diff --git a/src/room.rs b/src/room.rs index 3c2d74a..1ba837e 100644 --- a/src/room.rs +++ b/src/room.rs @@ -77,6 +77,7 @@ impl Room { let update = matches!(mode, BroadcastPresence::Join | BroadcastPresence::Update); let own_session = Session::try_from(presence)?; + let own_session_is_primary = own_occupant.get_primary_session() == &own_session; // All participants to new participant let presence_to_new = Presence::new(if leave { @@ -126,6 +127,11 @@ impl Room { if session.real == own_session.real { continue; } + // If own_session is leaving, and it is not the primary + // session, don't advertize the leave. + if leave && !own_session_is_primary { + continue; + } let presence = presence_to_old .clone() @@ -140,7 +146,11 @@ impl Room { .iter() .map(|session| MucItem { affiliation: Affiliation::Owner, - role: if leave { Role::None } else { Role::Moderator }, + role: if leave && session == &own_session { + Role::None + } else { + Role::Moderator + }, jid: Some(session.real.clone()), nick: None, actor: None, @@ -338,6 +348,11 @@ impl Room { .await?; occupant.remove_session(session.presence)?; + if occupant.iter().len() > 0 { + let _ = self + .occupants + .insert(session.participant.resource.clone(), occupant); + } } else { // TODO: Error } diff --git a/src/tests/presence.rs b/src/tests/presence.rs index 328359a..c093a78 100644 --- a/src/tests/presence.rs +++ b/src/tests/presence.rs @@ -17,8 +17,8 @@ use crate::component::TestComponent; use crate::handlers::handle_stanza; use crate::room::Room; use crate::tests::templates::{ - two_participant_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, ROOM1_BARE, - ROSA_FULL1, ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_ROOM1_PART, + new_room, two_participant_room, LOUISE_FULL1, LOUISE_FULL2, LOUISE_NICK, LOUISE_ROOM1_PART, + ROOM1_BARE, ROSA_FULL1, ROSA_ROOM1_PART, SUGAKO_FULL1, SUGAKO_NICK, SUGAKO_ROOM1_PART, }; use std::collections::{BTreeMap, HashMap}; @@ -669,9 +669,73 @@ async fn test_presence_update_not_joined() { handle_stanza(&mut component, &mut rooms).await.unwrap(); } -#[ignore] #[tokio::test] async fn test_presence_leave_msn() { // Ensure the occupant is still valid even though a first session left - todo!() + + let leave: Element = Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_FULL2.clone())) // Not the first session + .with_to(Jid::Full(LOUISE_ROOM1_PART.clone())) + .into(); + + let mut component = TestComponent::new(vec![leave]); + let mut rooms: HashMap = HashMap::new(); + rooms.insert( + ROOM1_BARE.clone(), + new_room( + ROOM1_BARE.clone(), + vec![ + (LOUISE_FULL1.clone(), LOUISE_NICK), + (SUGAKO_FULL1.clone(), SUGAKO_NICK), + (LOUISE_FULL2.clone(), LOUISE_NICK), + ], + ) + .await, + ); + + // 110 to the leaving session + component.expect( + Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_ROOM1_PART.clone())) + .with_to(Jid::Full(LOUISE_FULL2.clone())) + .with_payloads(vec![MucUser { + status: vec![MucStatus::SelfPresence], + items: { + let mut item1 = MucItem::new(Affiliation::Owner, Role::Moderator); + item1.jid = Some(LOUISE_FULL1.clone()); + let mut item2 = MucItem::new(Affiliation::Owner, Role::None); + item2.jid = Some(LOUISE_FULL2.clone()); + vec![item1, item2] + }, + } + .into()]), + ); + + // Unavailable presence to the other MSN session with @jid of the leaving session + component.expect( + Presence::new(PresenceType::Unavailable) + .with_from(Jid::Full(LOUISE_ROOM1_PART.clone())) + .with_to(Jid::Full(LOUISE_FULL1.clone())) + .with_payloads(vec![MucUser { + status: vec![], + items: { + let mut item1 = MucItem::new(Affiliation::Owner, Role::Moderator); + item1.jid = Some(LOUISE_FULL1.clone()); + let mut item2 = MucItem::new(Affiliation::Owner, Role::None); + item2.jid = Some(LOUISE_FULL2.clone()); + vec![item1, item2] + }, + } + .into()]), + ); + + // Presence update to other participants is not necessary as the main session (LOUISE_FULL1) is + // still alive + + handle_stanza(&mut component, &mut rooms).await.unwrap(); + + let occupants = &rooms.get(&ROOM1_BARE).unwrap().occupants; + let louise = occupants.get(LOUISE_NICK); + assert!(louise.is_some()); + assert_eq!(louise.unwrap().sessions.len(), 1); }