use rust_xmpp::{muc::Muc, protocol::ModuleResult}; use serde_json::json; const ROOM: &str = "room@rooms.example.org"; const COMMANDS: &str = "http://jabber.org/protocol/commands"; const AFF: &str = "http://prosody.im/protocol/muc#set-affiliation"; const DESTROY: &str = "http://prosody.im/protocol/muc#destroy"; fn send(m: &mut Muc, jid: &str, xml: &str) -> ModuleResult { let doc = roxmltree::Document::parse(xml).unwrap(); m.handle(jid, doc.root_element()).unwrap().unwrap() } fn room(m: &mut Muc) { send( m, "owner@example.org/r", &format!( "" ), ); send( m, "owner@example.org/r", &format!( "1" ), ); } fn register(m: &mut Muc, jid: &str, nick: &str) -> ModuleResult { send( m, jid, &format!( "http://jabber.org/protocol/muc#register{nick}" ), ) } #[test] fn optional_registration_grants_membership_and_enforces_reserved_nickname() { let root = tempfile::tempdir().unwrap(); let mut m = Muc::new(root.path().into(), "rooms.example.org".into()); m.set_component_options(json!({"allow_unaffiliated_register":true,"enforce_registered_nickname":true,"muc_registration_include_form":true}),false); room(&mut m); assert!(register(&mut m, "bob@example.org/r", "Reserved").replies[0].contains("type='result'")); let result = send( &mut m, "bob@example.org/r", &format!(""), ); assert!(result.replies[0].contains("type='result'>Reserved")); let joined = send( &mut m, "bob@example.org/r", &format!( "" ), ); assert!( joined .replies .iter() .any(|p| p.contains("/Reserved'") && p.contains("affiliation='member'")) ); let rename = send( &mut m, "bob@example.org/r", &format!(""), ); assert!(rename.replies[0].contains("not-acceptable")); let reregister = register(&mut m, "bob@example.org/r", "New"); assert!( reregister .deliveries .iter() .any(|(_, p)| p.contains("code='307'")) ); let message = send( &mut m, "bob@example.org/r", &format!("departed"), ); assert!(message.replies[0].contains("not-acceptable")); } #[test] fn default_registration_denies_unaffiliated_users() { let root = tempfile::tempdir().unwrap(); let mut m = Muc::new(root.path().into(), "rooms.example.org".into()); room(&mut m); assert!( register(&mut m, "bob@example.org/r", "Bob").replies[0].contains("registration-required") ); } fn start(m: &mut Muc, jid: &str, node: &str) -> String { let result = send( m, jid, &format!( "" ), ); let doc = roxmltree::Document::parse(&result.replies[0]).unwrap(); doc.descendants() .find(|n| n.has_tag_name((COMMANDS, "command"))) .unwrap() .attribute("sessionid") .unwrap() .to_owned() } fn submit(m: &mut Muc, jid: &str, node: &str, id: &str, fields: &str) -> ModuleResult { send( m, jid, &format!( "{node}{fields}" ), ) } #[test] fn administration_commands_recheck_authority_bind_sessions_and_update_live_rooms() { let root = tempfile::tempdir().unwrap(); let mut m = Muc::new(root.path().into(), "rooms.example.org".into()); m.set_component_options(json!({"admins":["admin@example.org","other@example.org"],"muc_tombstone_expiry":"0 seconds"}),false); room(&mut m); let outsider = send( &mut m, "stranger@example.org/r", &format!( "" ), ); assert!(!outsider.replies[0].contains("node='http://prosody.im/protocol/muc#destroy'")); let denied = send( &mut m, "stranger@example.org/r", &format!( "" ), ); assert!(denied.replies[0].contains("forbidden")); let id = start(&mut m, "admin@example.org/r", AFF); let fields = format!( "{ROOM}owner@example.orgoutcast" ); assert!( submit(&mut m, "other@example.org/r", AFF, &id, &fields).replies[0].contains("bad-request") ); let updated = submit(&mut m, "admin@example.org/r", AFF, &id, &fields); assert!(updated.replies[0].contains("status='completed'")); assert!( updated .deliveries .iter() .any(|(_, p)| p.contains("code='301'")) ); assert!( submit(&mut m, "admin@example.org/r", AFF, &id, &fields).replies[0].contains("bad-request") ); // A privileged component command may remove the final owner, just like // native room:set_affiliation(true,...); destroying such a room still works. let id = start(&mut m, "admin@example.org/r", DESTROY); assert!( submit( &mut m, "admin@example.org/r", DESTROY, &id, &format!("{ROOM}") ) .replies[0] .contains("status='completed'") ); let rejoin = send( &mut m, "new@example.org/r", &format!( "" ), ); assert!(rejoin.replies.iter().any(|p| p.contains("code='201'"))); } #[test] fn configured_tombstone_expiry_allows_immediate_recreation() { let root = tempfile::tempdir().unwrap(); let mut m = Muc::new(root.path().into(), "rooms.example.org".into()); m.set_component_options(json!({"muc_tombstone_expiry":"0 seconds"}), false); room(&mut m); send( &mut m, "owner@example.org/r", &format!( "" ), ); let joined = send( &mut m, "new@example.org/r", &format!( "" ), ); assert!(joined.replies.iter().any(|p| p.contains("code='201'"))); }