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