Skip to main content

xmpp_parsers/muc/
user.rs

1// Copyright (c) 2017 Maxime “pep” Buquet <pep@bouah.net>
2// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use xso::{AsXml, FromXml};
9
10use crate::message::MessagePayload;
11use crate::muc::mav::AffiliationsVersioning;
12use crate::ns;
13use crate::presence::PresencePayload;
14
15use jid::{FullJid, Jid};
16
17generate_attribute_enum!(
18/// Lists all of the possible status codes used in MUC presences.
19Status, "status", MUC_USER, "code", {
20    /// 100: Inform user that any occupant is allowed to see the user's full JID
21    NonAnonymousRoom => 100,
22
23    /// 101: Inform user that his or her affiliation changed while not in the room
24    AffiliationChange => 101,
25
26    /// 102: Inform occupants that room now shows unavailable members
27    ConfigShowsUnavailableMembers => 102,
28
29    /// 103: Inform occupants that room now does not show unavailable members
30    ConfigHidesUnavailableMembers => 103,
31
32    /// 104: Inform occupants that a non-privacy-related room configuration change has occurred
33    ConfigNonPrivacyRelated => 104,
34
35    /// 110: Inform user that presence refers to itself
36    SelfPresence => 110,
37
38    /// 170: Inform occupants that room logging is now enabled
39    ConfigRoomLoggingEnabled => 170,
40
41    /// 171: Inform occupants that room logging is now disabled
42    ConfigRoomLoggingDisabled => 171,
43
44    /// 172: Inform occupants that the room is now non-anonymous
45    ConfigRoomNonAnonymous => 172,
46
47    /// 173: Inform occupants that the room is now semi-anonymous
48    ConfigRoomSemiAnonymous => 173,
49
50    /// 201: Inform user that a new room has been created
51    RoomHasBeenCreated => 201,
52
53    /// 210: Inform user that service has assigned or modified occupant's roomnick
54    AssignedNick => 210,
55
56    /// 301: Inform user that they have been banned from the room
57    Banned => 301,
58
59    /// 303: Inform all occupants of new room nickname
60    NewNick => 303,
61
62    /// 307: Inform user that they have been kicked from the room
63    Kicked => 307,
64
65    /// 321: Inform user that they are being removed from the room
66    /// because of an affiliation change
67    RemovalFromRoom => 321,
68
69    /// 322: Inform user that they are being removed from the room
70    /// because the room has been changed to members-only and the
71    /// user is not a member
72    ConfigMembersOnly => 322,
73
74    /// 332: Inform user that they are being removed from the room
75    /// because the MUC service is being shut down
76    ServiceShutdown => 332,
77
78    /// 333: Inform user that they are being removed from the room for technical reasons
79    ServiceErrorKick => 333,
80});
81
82/// Optional \<actor/\> element used in \<item/\> elements inside presence stanzas of type
83/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
84/// purposes. -- CHANGELOG  0.17 (2002-10-23)
85///
86/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
87/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
88#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
89#[xml(namespace = ns::MUC_USER, name = "actor")]
90pub struct Actor {
91    /// The full JID associated with this user.
92    #[xml(attribute(default))]
93    jid: Option<FullJid>,
94
95    /// The nickname of this user.
96    #[xml(attribute(default))]
97    nick: Option<String>,
98}
99
100/// Used to continue a one-to-one discussion in a room, with more than one
101/// participant.
102#[derive(FromXml, AsXml, PartialEq, Debug, Clone)]
103#[xml(namespace = ns::MUC_USER, name = "continue")]
104pub struct Continue {
105    /// The thread to continue in this room.
106    #[xml(attribute(default))]
107    pub thread: Option<String>,
108}
109
110generate_elem_id!(
111    /// A reason for inviting, declining, etc. a request.
112    Reason,
113    "reason",
114    MUC_USER
115);
116
117generate_attribute!(
118    /// The affiliation of an entity with a room, which isn’t tied to its
119    /// presence in it.
120    Affiliation, "affiliation", {
121        /// The user who created the room, or who got appointed by its creator
122        /// to be their equal.
123        Owner => "owner",
124
125        /// A user who has been empowered by an owner to do administrative
126        /// operations.
127        Admin => "admin",
128
129        /// A user who is whitelisted to speak in moderated rooms, or to join a
130        /// member-only room.
131        Member => "member",
132
133        /// A user who has been banned from this room.
134        Outcast => "outcast",
135
136        /// A normal participant.
137        None => "none",
138    }, Default = None
139);
140
141generate_attribute!(
142    /// The current role of an entity in a room, it can be changed by an owner
143    /// or an administrator but will be lost once they leave the room.
144    Role, "role", {
145        /// This user can kick other participants, as well as grant and revoke
146        /// them voice.
147        Moderator => "moderator",
148
149        /// A user who can speak in this room.
150        Participant => "participant",
151
152        /// A user who cannot speak in this room, and must request voice before
153        /// doing so.
154        Visitor => "visitor",
155
156        /// A user who is absent from the room.
157        None => "none",
158    }, Default = None
159);
160
161/// An item representing a user in a room.
162#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
163#[xml(namespace = ns::MUC_USER, name = "item")]
164pub struct Item {
165    /// The affiliation of this user with the room.
166    #[xml(attribute)]
167    pub affiliation: Affiliation,
168
169    /// The real JID of this user, if you are allowed to see it.
170    #[xml(attribute(default))]
171    pub jid: Option<FullJid>,
172
173    /// The current nickname of this user.
174    #[xml(attribute(default))]
175    pub nick: Option<String>,
176
177    /// The current role of this user.
178    #[xml(attribute)]
179    pub role: Role,
180
181    /// The actor affected by this item.
182    #[xml(child(default))]
183    pub actor: Option<Actor>,
184
185    /// Whether this continues a one-to-one discussion.
186    #[xml(child(default))]
187    pub continue_: Option<Continue>,
188
189    /// A reason for this item.
190    #[xml(child(default))]
191    pub reason: Option<Reason>,
192}
193
194impl Item {
195    /// Creates a new item with the given affiliation and role.
196    pub fn new(affiliation: Affiliation, role: Role) -> Item {
197        Item {
198            affiliation,
199            role,
200            jid: None,
201            nick: None,
202            actor: None,
203            continue_: None,
204            reason: None,
205        }
206    }
207
208    /// Set a jid for this Item
209    pub fn with_jid(mut self, jid: FullJid) -> Item {
210        self.jid = Some(jid);
211        self
212    }
213
214    /// Set a nick for this Item
215    pub fn with_nick<S: Into<String>>(mut self, nick: S) -> Item {
216        self.nick = Some(nick.into());
217        self
218    }
219
220    /// Set an actor for this Item
221    pub fn with_actor(mut self, actor: Actor) -> Item {
222        self.actor = Some(actor);
223        self
224    }
225
226    /// Set a continue value for this Item
227    pub fn with_continue<S: Into<String>>(mut self, continue_: S) -> Item {
228        self.continue_ = Some(Continue {
229            thread: Some(continue_.into()),
230        });
231        self
232    }
233
234    /// Set a reason for this Item
235    pub fn with_reason<S: Into<String>>(mut self, reason: S) -> Item {
236        self.reason = Some(Reason(reason.into()));
237        self
238    }
239}
240
241/// Mediated invite
242#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
243#[xml(namespace = ns::MUC_USER, name = "invite")]
244pub struct Invite {
245    /// Sender.
246    ///
247    /// This is only populated for invites which have been mediated by a MUC
248    /// and sent to the invitee.
249    #[xml(attribute(default))]
250    pub from: Option<Jid>,
251
252    /// Recipient.
253    ///
254    /// This is only populated for requests to mediate an invite through a
255    /// MUC, before forwarding it to the invitee.
256    #[xml(attribute(default))]
257    pub to: Option<Jid>,
258
259    /// The optional reason for the invite.
260    #[xml(extract(name = "reason", default, fields(text(type_ = String))))]
261    pub reason: Option<String>,
262}
263
264/// Rejection of a mediated invite.
265#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
266#[xml(namespace = ns::MUC_USER, name = "decline")]
267pub struct Decline {
268    /// Sender.
269    ///
270    /// This is only populated for rejections which have been mediated by a
271    /// MUC and sent to the inviter.
272    #[xml(attribute(default))]
273    pub from: Option<Jid>,
274
275    /// Recipient.
276    ///
277    /// This is only populated for requests to decline an invite through a
278    /// MUC, before forwarding it to the inviter.
279    #[xml(attribute(default))]
280    pub to: Option<Jid>,
281
282    /// The optional reason for the rejection.
283    #[xml(extract(name = "reason", default, fields(text(type_ = String))))]
284    pub reason: Option<String>,
285}
286
287/// The main muc#user element.
288#[derive(FromXml, AsXml, Debug, PartialEq, Clone)]
289#[xml(namespace = ns::MUC_USER, name = "x")]
290pub struct MucUser {
291    /// List of statuses applying to this item.
292    #[xml(child(n = ..))]
293    pub status: Vec<Status>,
294
295    /// List of items.
296    #[xml(child(n = ..))]
297    pub items: Vec<Item>,
298
299    /// A mediated invite
300    #[xml(child(default))]
301    pub invite: Option<Invite>,
302
303    /// A mediated invite rejection
304    #[xml(child(default))]
305    pub decline: Option<Decline>,
306
307    /// From XEP-0463: MUC Affiliations Versioning
308    #[xml(child(default))]
309    pub affiliations: Option<AffiliationsVersioning>,
310}
311
312impl Default for MucUser {
313    fn default() -> Self {
314        Self::new()
315    }
316}
317
318impl MucUser {
319    /// Creates an empty MucUser
320    pub fn new() -> MucUser {
321        MucUser {
322            status: vec![],
323            items: vec![],
324            invite: None,
325            decline: None,
326            affiliations: None,
327        }
328    }
329
330    /// Set statuses for this MucUser
331    pub fn with_statuses(mut self, status: Vec<Status>) -> MucUser {
332        self.status = status;
333        self
334    }
335
336    /// Set items for this MucUser
337    pub fn with_items(mut self, items: Vec<Item>) -> MucUser {
338        self.items = items;
339        self
340    }
341}
342
343impl MessagePayload for MucUser {}
344impl PresencePayload for MucUser {}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::message::Message;
350    use crate::presence::{Presence, Type as PresenceType};
351    use jid::Jid;
352    use minidom::Element;
353    use xso::error::{Error, FromElementError};
354
355    #[cfg(target_pointer_width = "32")]
356    #[test]
357    fn test_size() {
358        assert_size!(Status, 1);
359        assert_size!(Actor, 28);
360        assert_size!(Continue, 12);
361        assert_size!(Reason, 12);
362        assert_size!(Affiliation, 1);
363        assert_size!(Role, 1);
364        assert_size!(Item, 84);
365        assert_size!(Invite, 44);
366        assert_size!(Decline, 44);
367        assert_size!(MucUser, 136);
368    }
369
370    #[cfg(target_pointer_width = "64")]
371    #[test]
372    fn test_size() {
373        assert_size!(Status, 1);
374        assert_size!(Actor, 56);
375        assert_size!(Continue, 24);
376        assert_size!(Reason, 24);
377        assert_size!(Affiliation, 1);
378        assert_size!(Role, 1);
379        assert_size!(Item, 168);
380        assert_size!(Invite, 88);
381        assert_size!(Decline, 88);
382        assert_size!(MucUser, 272);
383    }
384
385    #[test]
386    fn test_simple() {
387        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
388            .parse()
389            .unwrap();
390        MucUser::try_from(elem).unwrap();
391    }
392
393    #[test]
394    fn statuses_and_items() {
395        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
396                <status code='101'/>
397                <status code='102'/>
398                <item affiliation='member' role='moderator'/>
399            </x>"
400            .parse()
401            .unwrap();
402        let muc_user = MucUser::try_from(elem).unwrap();
403        assert_eq!(muc_user.status.len(), 2);
404        assert_eq!(muc_user.status[0], Status::AffiliationChange);
405        assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
406        assert_eq!(muc_user.items.len(), 1);
407        assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
408        assert_eq!(muc_user.items[0].role, Role::Moderator);
409    }
410
411    #[test]
412    #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
413    fn test_invalid_child() {
414        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
415                <coucou/>
416            </x>"
417            .parse()
418            .unwrap();
419        let error = MucUser::try_from(elem).unwrap_err();
420        let message = match error {
421            FromElementError::Invalid(Error::Other(string)) => string,
422            _ => panic!(),
423        };
424        assert_eq!(message, "Unknown child in MucUser element.");
425    }
426
427    #[test]
428    fn test_serialise() {
429        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
430            .parse()
431            .unwrap();
432        let muc = MucUser {
433            status: vec![],
434            items: vec![],
435            invite: None,
436            decline: None,
437            affiliations: None,
438        };
439        let elem2 = muc.into();
440        assert_eq!(elem, elem2);
441    }
442
443    #[cfg(feature = "pedantic")]
444    #[test]
445    fn test_invalid_attribute() {
446        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
447            .parse()
448            .unwrap();
449        let error = MucUser::try_from(elem).unwrap_err();
450        let message = match error {
451            FromElementError::Invalid(Error::Other(string)) => string,
452            _ => panic!(),
453        };
454        assert_eq!(message, "Unknown attribute in MucUser element.");
455    }
456
457    #[test]
458    fn test_status_simple() {
459        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
460            .parse()
461            .unwrap();
462        Status::try_from(elem).unwrap();
463    }
464
465    #[test]
466    fn test_status_invalid() {
467        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
468            .parse()
469            .unwrap();
470        let error = Status::try_from(elem).unwrap_err();
471        let message = match error {
472            FromElementError::Invalid(Error::Other(string)) => string,
473            _ => panic!(),
474        };
475        assert_eq!(message, "Required attribute 'code' missing.");
476    }
477
478    #[cfg(feature = "pedantic")]
479    #[test]
480    fn test_status_invalid_child() {
481        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
482                <foo/>
483            </status>"
484            .parse()
485            .unwrap();
486        let error = Status::try_from(elem).unwrap_err();
487        let message = match error {
488            FromElementError::Invalid(Error::Other(string)) => string,
489            _ => panic!(),
490        };
491        assert_eq!(message, "Unknown child in status element.");
492    }
493
494    #[test]
495    fn test_status_simple_code() {
496        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
497            .parse()
498            .unwrap();
499        let status = Status::try_from(elem).unwrap();
500        assert_eq!(status, Status::Kicked);
501    }
502
503    #[test]
504    fn test_status_invalid_code() {
505        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
506            .parse()
507            .unwrap();
508        let error = Status::try_from(elem).unwrap_err();
509        let message = match error {
510            FromElementError::Invalid(Error::Other(string)) => string,
511            _ => panic!(),
512        };
513        assert_eq!(message, "Invalid status code value.");
514    }
515
516    #[test]
517    fn test_status_invalid_code2() {
518        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
519            .parse()
520            .unwrap();
521        let error = Status::try_from(elem).unwrap_err();
522        let error = match error {
523            FromElementError::Invalid(Error::TextParseError(error))
524                if error.is::<core::num::ParseIntError>() =>
525            {
526                error
527            }
528            _ => panic!(),
529        };
530        assert_eq!(error.to_string(), "invalid digit found in string");
531    }
532
533    // This test is now ignored because we switched to a representation where we can’t currently
534    // validate whether one of the required attributes is present or not.
535    #[test]
536    #[ignore]
537    fn test_actor_required_attributes() {
538        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
539            .parse()
540            .unwrap();
541        let error = Actor::try_from(elem).unwrap_err();
542        let message = match error {
543            FromElementError::Invalid(Error::Other(string)) => string,
544            _ => panic!(),
545        };
546        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
547    }
548
549    #[test]
550    fn test_actor_jid() {
551        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
552                   jid='foo@bar/baz'/>"
553            .parse()
554            .unwrap();
555        let actor = Actor::try_from(elem).unwrap();
556        assert_eq!(actor.jid, Some("foo@bar/baz".parse::<FullJid>().unwrap()));
557        assert_eq!(actor.nick, None);
558    }
559
560    #[test]
561    fn test_actor_nick() {
562        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
563            .parse()
564            .unwrap();
565        let actor = Actor::try_from(elem).unwrap();
566        assert_eq!(actor.nick, Some("baz".to_owned()));
567        assert_eq!(actor.jid, None);
568    }
569
570    #[test]
571    fn test_continue_simple() {
572        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
573            .parse()
574            .unwrap();
575        Continue::try_from(elem).unwrap();
576    }
577
578    #[test]
579    fn test_continue_thread_attribute() {
580        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
581                      thread='foo'/>"
582            .parse()
583            .unwrap();
584        let continue_ = Continue::try_from(elem).unwrap();
585        assert_eq!(continue_.thread, Some("foo".to_owned()));
586    }
587
588    #[test]
589    #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
590    fn test_continue_invalid() {
591        let elem: Element =
592            "<continue xmlns='http://jabber.org/protocol/muc#user'><foobar/></continue>"
593                .parse()
594                .unwrap();
595        let continue_ = Continue::try_from(elem).unwrap_err();
596        let message = match continue_ {
597            FromElementError::Invalid(Error::Other(string)) => string,
598            _ => panic!(),
599        };
600        assert_eq!(message, "Unknown child in Continue element.".to_owned());
601    }
602
603    #[test]
604    fn test_reason_simple() {
605        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
606            .parse()
607            .unwrap();
608        let elem2 = elem.clone();
609        let reason = Reason::try_from(elem).unwrap();
610        assert_eq!(reason.0, "Reason".to_owned());
611
612        let elem3 = reason.into();
613        assert_eq!(elem2, elem3);
614    }
615
616    #[cfg(feature = "pedantic")]
617    #[test]
618    fn test_reason_invalid_attribute() {
619        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
620            .parse()
621            .unwrap();
622        let error = Reason::try_from(elem).unwrap_err();
623        let message = match error {
624            FromElementError::Invalid(Error::Other(string)) => string,
625            _ => panic!(),
626        };
627        assert_eq!(message, "Unknown attribute in Reason element.".to_owned());
628    }
629
630    #[cfg(feature = "pedantic")]
631    #[test]
632    fn test_reason_invalid() {
633        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
634                <foobar/>
635            </reason>"
636            .parse()
637            .unwrap();
638        let error = Reason::try_from(elem).unwrap_err();
639        let message = match error {
640            FromElementError::Invalid(Error::Other(string)) => string,
641            _ => panic!(),
642        };
643        assert_eq!(message, "Unknown child in Reason element.".to_owned());
644    }
645
646    #[cfg(feature = "pedantic")]
647    #[test]
648    fn test_item_invalid_attr() {
649        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
650                  affiliation='member'
651                  role='moderator'
652                  foo='bar'/>"
653            .parse()
654            .unwrap();
655        let error = Item::try_from(elem).unwrap_err();
656        let message = match error {
657            FromElementError::Invalid(Error::Other(string)) => string,
658            _ => panic!(),
659        };
660        assert_eq!(message, "Unknown attribute in Item element.".to_owned());
661    }
662
663    #[test]
664    fn test_item_affiliation_role_attr() {
665        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
666                  affiliation='member'
667                  role='moderator'/>"
668            .parse()
669            .unwrap();
670        Item::try_from(elem).unwrap();
671    }
672
673    #[test]
674    fn test_item_affiliation_role_invalid_attr() {
675        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
676                  affiliation='member'/>"
677            .parse()
678            .unwrap();
679        let error = Item::try_from(elem).unwrap_err();
680        let message = match error {
681            FromElementError::Invalid(Error::Other(string)) => string,
682            _ => panic!(),
683        };
684        assert_eq!(
685            message,
686            "Required attribute field 'role' on Item element missing.".to_owned()
687        );
688    }
689
690    #[test]
691    fn test_item_nick_attr() {
692        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
693                  affiliation='member'
694                  role='moderator'
695                  nick='foobar'/>"
696            .parse()
697            .unwrap();
698        let item = Item::try_from(elem).unwrap();
699        match item {
700            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
701        }
702    }
703
704    #[test]
705    fn test_item_affiliation_role_invalid_attr2() {
706        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
707                  role='moderator'/>"
708            .parse()
709            .unwrap();
710        let error = Item::try_from(elem).unwrap_err();
711        let message = match error {
712            FromElementError::Invalid(Error::Other(string)) => string,
713            _ => panic!(),
714        };
715        assert_eq!(
716            message,
717            "Required attribute field 'affiliation' on Item element missing.".to_owned()
718        );
719    }
720
721    #[test]
722    fn test_item_role_actor_child() {
723        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
724                  affiliation='member'
725                  role='moderator'>
726                <actor nick='foobar'/>
727            </item>"
728            .parse()
729            .unwrap();
730        let item = Item::try_from(elem).unwrap();
731        let Item { actor, .. } = item;
732        let actor = actor.unwrap();
733        assert_eq!(actor.nick, Some("foobar".to_owned()));
734        assert_eq!(actor.jid, None);
735    }
736
737    #[test]
738    fn test_item_role_continue_child() {
739        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
740                  affiliation='member'
741                  role='moderator'>
742                <continue thread='foobar'/>
743            </item>"
744            .parse()
745            .unwrap();
746        let item = Item::try_from(elem).unwrap();
747        let continue_1 = Continue {
748            thread: Some("foobar".to_owned()),
749        };
750        match item {
751            Item {
752                continue_: Some(continue_2),
753                ..
754            } => assert_eq!(continue_2.thread, continue_1.thread),
755            _ => panic!(),
756        }
757    }
758
759    #[test]
760    fn test_item_role_reason_child() {
761        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
762                  affiliation='member'
763                  role='moderator'>
764                <reason>foobar</reason>
765            </item>"
766            .parse()
767            .unwrap();
768        let item = Item::try_from(elem).unwrap();
769        match item {
770            Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
771        }
772    }
773
774    #[test]
775    fn test_serialize_item() {
776        let reference: Element = "<item xmlns='http://jabber.org/protocol/muc#user' affiliation='member' role='moderator'><actor nick='foobar'/><continue thread='foobar'/><reason>foobar</reason></item>"
777        .parse()
778        .unwrap();
779
780        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
781            .parse()
782            .unwrap();
783        let actor = Actor::try_from(elem).unwrap();
784
785        let elem: Element =
786            "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
787                .parse()
788                .unwrap();
789        let continue_ = Continue::try_from(elem).unwrap();
790
791        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
792            .parse()
793            .unwrap();
794        let reason = Reason::try_from(elem).unwrap();
795
796        let item = Item {
797            affiliation: Affiliation::Member,
798            role: Role::Moderator,
799            jid: None,
800            nick: None,
801            actor: Some(actor),
802            reason: Some(reason),
803            continue_: Some(continue_),
804        };
805
806        let serialized: Element = item.into();
807        assert_eq!(serialized, reference);
808    }
809
810    #[test]
811    fn presence_payload() {
812        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
813            .parse()
814            .unwrap();
815        let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
816        assert_eq!(presence.payloads.len(), 1);
817    }
818
819    #[test]
820    fn message_payload() {
821        let jid: Jid = Jid::new("louise@example.com").unwrap();
822        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
823            .parse()
824            .unwrap();
825        let message = Message::new(jid).with_payloads(vec![elem]);
826        assert_eq!(message.payloads.len(), 1);
827    }
828}