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, Default, 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 MucUser {
313    /// Creates an empty MucUser
314    pub fn new() -> MucUser {
315        MucUser::default()
316    }
317
318    /// Set statuses for this MucUser
319    pub fn with_statuses(mut self, status: Vec<Status>) -> MucUser {
320        self.status = status;
321        self
322    }
323
324    /// Set items for this MucUser
325    pub fn with_items(mut self, items: Vec<Item>) -> MucUser {
326        self.items = items;
327        self
328    }
329}
330
331impl MessagePayload for MucUser {}
332impl PresencePayload for MucUser {}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337    use crate::message::Message;
338    use crate::presence::{Presence, Type as PresenceType};
339    use jid::Jid;
340    use minidom::Element;
341    use xso::error::{Error, FromElementError};
342
343    #[cfg(target_pointer_width = "32")]
344    #[test]
345    fn test_size() {
346        assert_size!(Status, 1);
347        assert_size!(Actor, 28);
348        assert_size!(Continue, 12);
349        assert_size!(Reason, 12);
350        assert_size!(Affiliation, 1);
351        assert_size!(Role, 1);
352        assert_size!(Item, 84);
353        assert_size!(Invite, 44);
354        assert_size!(Decline, 44);
355        assert_size!(MucUser, 136);
356    }
357
358    #[cfg(target_pointer_width = "64")]
359    #[test]
360    fn test_size() {
361        assert_size!(Status, 1);
362        assert_size!(Actor, 56);
363        assert_size!(Continue, 24);
364        assert_size!(Reason, 24);
365        assert_size!(Affiliation, 1);
366        assert_size!(Role, 1);
367        assert_size!(Item, 168);
368        assert_size!(Invite, 88);
369        assert_size!(Decline, 88);
370        assert_size!(MucUser, 272);
371    }
372
373    #[test]
374    fn test_simple() {
375        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
376            .parse()
377            .unwrap();
378        MucUser::try_from(elem).unwrap();
379    }
380
381    #[test]
382    fn statuses_and_items() {
383        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
384                <status code='101'/>
385                <status code='102'/>
386                <item affiliation='member' role='moderator'/>
387            </x>"
388            .parse()
389            .unwrap();
390        let muc_user = MucUser::try_from(elem).unwrap();
391        assert_eq!(muc_user.status.len(), 2);
392        assert_eq!(muc_user.status[0], Status::AffiliationChange);
393        assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
394        assert_eq!(muc_user.items.len(), 1);
395        assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
396        assert_eq!(muc_user.items[0].role, Role::Moderator);
397    }
398
399    #[test]
400    #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
401    fn test_invalid_child() {
402        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'>
403                <coucou/>
404            </x>"
405            .parse()
406            .unwrap();
407        let error = MucUser::try_from(elem).unwrap_err();
408        let message = match error {
409            FromElementError::Invalid(Error::Other(string)) => string,
410            _ => panic!(),
411        };
412        assert_eq!(message, "Unknown child in MucUser element.");
413    }
414
415    #[test]
416    fn test_serialise() {
417        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
418            .parse()
419            .unwrap();
420        let muc = MucUser::new();
421        let elem2 = muc.into();
422        assert_eq!(elem, elem2);
423    }
424
425    #[cfg(feature = "pedantic")]
426    #[test]
427    fn test_invalid_attribute() {
428        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>"
429            .parse()
430            .unwrap();
431        let error = MucUser::try_from(elem).unwrap_err();
432        let message = match error {
433            FromElementError::Invalid(Error::Other(string)) => string,
434            _ => panic!(),
435        };
436        assert_eq!(message, "Unknown attribute in MucUser element.");
437    }
438
439    #[test]
440    fn test_status_simple() {
441        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'/>"
442            .parse()
443            .unwrap();
444        Status::try_from(elem).unwrap();
445    }
446
447    #[test]
448    fn test_status_invalid() {
449        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user'/>"
450            .parse()
451            .unwrap();
452        let error = Status::try_from(elem).unwrap_err();
453        let message = match error {
454            FromElementError::Invalid(Error::Other(string)) => string,
455            _ => panic!(),
456        };
457        assert_eq!(message, "Required attribute 'code' missing.");
458    }
459
460    #[cfg(feature = "pedantic")]
461    #[test]
462    fn test_status_invalid_child() {
463        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='110'>
464                <foo/>
465            </status>"
466            .parse()
467            .unwrap();
468        let error = Status::try_from(elem).unwrap_err();
469        let message = match error {
470            FromElementError::Invalid(Error::Other(string)) => string,
471            _ => panic!(),
472        };
473        assert_eq!(message, "Unknown child in status element.");
474    }
475
476    #[test]
477    fn test_status_simple_code() {
478        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='307'/>"
479            .parse()
480            .unwrap();
481        let status = Status::try_from(elem).unwrap();
482        assert_eq!(status, Status::Kicked);
483    }
484
485    #[test]
486    fn test_status_invalid_code() {
487        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='666'/>"
488            .parse()
489            .unwrap();
490        let error = Status::try_from(elem).unwrap_err();
491        let message = match error {
492            FromElementError::Invalid(Error::Other(string)) => string,
493            _ => panic!(),
494        };
495        assert_eq!(message, "Invalid status code value.");
496    }
497
498    #[test]
499    fn test_status_invalid_code2() {
500        let elem: Element = "<status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>"
501            .parse()
502            .unwrap();
503        let error = Status::try_from(elem).unwrap_err();
504        let error = match error {
505            FromElementError::Invalid(Error::TextParseError(error))
506                if error.is::<core::num::ParseIntError>() =>
507            {
508                error
509            }
510            _ => panic!(),
511        };
512        assert_eq!(error.to_string(), "invalid digit found in string");
513    }
514
515    // This test is now ignored because we switched to a representation where we can’t currently
516    // validate whether one of the required attributes is present or not.
517    #[test]
518    #[ignore]
519    fn test_actor_required_attributes() {
520        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'/>"
521            .parse()
522            .unwrap();
523        let error = Actor::try_from(elem).unwrap_err();
524        let message = match error {
525            FromElementError::Invalid(Error::Other(string)) => string,
526            _ => panic!(),
527        };
528        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
529    }
530
531    #[test]
532    fn test_actor_jid() {
533        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user'
534                   jid='foo@bar/baz'/>"
535            .parse()
536            .unwrap();
537        let actor = Actor::try_from(elem).unwrap();
538        assert_eq!(actor.jid, Some("foo@bar/baz".parse::<FullJid>().unwrap()));
539        assert_eq!(actor.nick, None);
540    }
541
542    #[test]
543    fn test_actor_nick() {
544        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>"
545            .parse()
546            .unwrap();
547        let actor = Actor::try_from(elem).unwrap();
548        assert_eq!(actor.nick, Some("baz".to_owned()));
549        assert_eq!(actor.jid, None);
550    }
551
552    #[test]
553    fn test_continue_simple() {
554        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'/>"
555            .parse()
556            .unwrap();
557        Continue::try_from(elem).unwrap();
558    }
559
560    #[test]
561    fn test_continue_thread_attribute() {
562        let elem: Element = "<continue xmlns='http://jabber.org/protocol/muc#user'
563                      thread='foo'/>"
564            .parse()
565            .unwrap();
566        let continue_ = Continue::try_from(elem).unwrap();
567        assert_eq!(continue_.thread, Some("foo".to_owned()));
568    }
569
570    #[test]
571    #[cfg_attr(not(feature = "pedantic"), should_panic = "Result::unwrap_err")]
572    fn test_continue_invalid() {
573        let elem: Element =
574            "<continue xmlns='http://jabber.org/protocol/muc#user'><foobar/></continue>"
575                .parse()
576                .unwrap();
577        let continue_ = Continue::try_from(elem).unwrap_err();
578        let message = match continue_ {
579            FromElementError::Invalid(Error::Other(string)) => string,
580            _ => panic!(),
581        };
582        assert_eq!(message, "Unknown child in Continue element.".to_owned());
583    }
584
585    #[test]
586    fn test_reason_simple() {
587        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
588            .parse()
589            .unwrap();
590        let elem2 = elem.clone();
591        let reason = Reason::try_from(elem).unwrap();
592        assert_eq!(reason.0, "Reason".to_owned());
593
594        let elem3 = reason.into();
595        assert_eq!(elem2, elem3);
596    }
597
598    #[cfg(feature = "pedantic")]
599    #[test]
600    fn test_reason_invalid_attribute() {
601        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>"
602            .parse()
603            .unwrap();
604        let error = Reason::try_from(elem).unwrap_err();
605        let message = match error {
606            FromElementError::Invalid(Error::Other(string)) => string,
607            _ => panic!(),
608        };
609        assert_eq!(message, "Unknown attribute in Reason element.".to_owned());
610    }
611
612    #[cfg(feature = "pedantic")]
613    #[test]
614    fn test_reason_invalid() {
615        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>
616                <foobar/>
617            </reason>"
618            .parse()
619            .unwrap();
620        let error = Reason::try_from(elem).unwrap_err();
621        let message = match error {
622            FromElementError::Invalid(Error::Other(string)) => string,
623            _ => panic!(),
624        };
625        assert_eq!(message, "Unknown child in Reason element.".to_owned());
626    }
627
628    #[cfg(feature = "pedantic")]
629    #[test]
630    fn test_item_invalid_attr() {
631        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
632                  affiliation='member'
633                  role='moderator'
634                  foo='bar'/>"
635            .parse()
636            .unwrap();
637        let error = Item::try_from(elem).unwrap_err();
638        let message = match error {
639            FromElementError::Invalid(Error::Other(string)) => string,
640            _ => panic!(),
641        };
642        assert_eq!(message, "Unknown attribute in Item element.".to_owned());
643    }
644
645    #[test]
646    fn test_item_affiliation_role_attr() {
647        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
648                  affiliation='member'
649                  role='moderator'/>"
650            .parse()
651            .unwrap();
652        Item::try_from(elem).unwrap();
653    }
654
655    #[test]
656    fn test_item_affiliation_role_invalid_attr() {
657        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
658                  affiliation='member'/>"
659            .parse()
660            .unwrap();
661        let error = Item::try_from(elem).unwrap_err();
662        let message = match error {
663            FromElementError::Invalid(Error::Other(string)) => string,
664            _ => panic!(),
665        };
666        assert_eq!(
667            message,
668            "Required attribute field 'role' on Item element missing.".to_owned()
669        );
670    }
671
672    #[test]
673    fn test_item_nick_attr() {
674        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
675                  affiliation='member'
676                  role='moderator'
677                  nick='foobar'/>"
678            .parse()
679            .unwrap();
680        let item = Item::try_from(elem).unwrap();
681        match item {
682            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
683        }
684    }
685
686    #[test]
687    fn test_item_affiliation_role_invalid_attr2() {
688        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
689                  role='moderator'/>"
690            .parse()
691            .unwrap();
692        let error = Item::try_from(elem).unwrap_err();
693        let message = match error {
694            FromElementError::Invalid(Error::Other(string)) => string,
695            _ => panic!(),
696        };
697        assert_eq!(
698            message,
699            "Required attribute field 'affiliation' on Item element missing.".to_owned()
700        );
701    }
702
703    #[test]
704    fn test_item_role_actor_child() {
705        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
706                  affiliation='member'
707                  role='moderator'>
708                <actor nick='foobar'/>
709            </item>"
710            .parse()
711            .unwrap();
712        let item = Item::try_from(elem).unwrap();
713        let Item { actor, .. } = item;
714        let actor = actor.unwrap();
715        assert_eq!(actor.nick, Some("foobar".to_owned()));
716        assert_eq!(actor.jid, None);
717    }
718
719    #[test]
720    fn test_item_role_continue_child() {
721        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
722                  affiliation='member'
723                  role='moderator'>
724                <continue thread='foobar'/>
725            </item>"
726            .parse()
727            .unwrap();
728        let item = Item::try_from(elem).unwrap();
729        let continue_1 = Continue {
730            thread: Some("foobar".to_owned()),
731        };
732        match item {
733            Item {
734                continue_: Some(continue_2),
735                ..
736            } => assert_eq!(continue_2.thread, continue_1.thread),
737            _ => panic!(),
738        }
739    }
740
741    #[test]
742    fn test_item_role_reason_child() {
743        let elem: Element = "<item xmlns='http://jabber.org/protocol/muc#user'
744                  affiliation='member'
745                  role='moderator'>
746                <reason>foobar</reason>
747            </item>"
748            .parse()
749            .unwrap();
750        let item = Item::try_from(elem).unwrap();
751        match item {
752            Item { reason, .. } => assert_eq!(reason, Some(Reason("foobar".to_owned()))),
753        }
754    }
755
756    #[test]
757    fn test_serialize_item() {
758        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>"
759        .parse()
760        .unwrap();
761
762        let elem: Element = "<actor xmlns='http://jabber.org/protocol/muc#user' nick='foobar'/>"
763            .parse()
764            .unwrap();
765        let actor = Actor::try_from(elem).unwrap();
766
767        let elem: Element =
768            "<continue xmlns='http://jabber.org/protocol/muc#user' thread='foobar'/>"
769                .parse()
770                .unwrap();
771        let continue_ = Continue::try_from(elem).unwrap();
772
773        let elem: Element = "<reason xmlns='http://jabber.org/protocol/muc#user'>foobar</reason>"
774            .parse()
775            .unwrap();
776        let reason = Reason::try_from(elem).unwrap();
777
778        let item = Item {
779            affiliation: Affiliation::Member,
780            role: Role::Moderator,
781            jid: None,
782            nick: None,
783            actor: Some(actor),
784            reason: Some(reason),
785            continue_: Some(continue_),
786        };
787
788        let serialized: Element = item.into();
789        assert_eq!(serialized, reference);
790    }
791
792    #[test]
793    fn presence_payload() {
794        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
795            .parse()
796            .unwrap();
797        let presence = Presence::new(PresenceType::None).with_payloads(vec![elem]);
798        assert_eq!(presence.payloads.len(), 1);
799    }
800
801    #[test]
802    fn message_payload() {
803        let jid: Jid = Jid::new("louise@example.com").unwrap();
804        let elem: Element = "<x xmlns='http://jabber.org/protocol/muc#user'/>"
805            .parse()
806            .unwrap();
807        let message = Message::new(jid).with_payloads(vec![elem]);
808        assert_eq!(message.payloads.len(), 1);
809    }
810}