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