source: wokkel/test/test_xmppim.py @ 174:c0f51d95bf0f

Last change on this file since 174:c0f51d95bf0f was 174:c0f51d95bf0f, checked in by Ralph Meijer <ralphm@…>, 8 years ago

Add support for adding and updating roster items.

Roster items can be added or updated by sending a roster set request using the
new wokkel.xmppim.RosterClientProtocol.setItem. It takes a RosterItem as
the only argument. Note that changes in presence subscriptions for the contact
need to be done using presence as provided by
wokkel.xmppim.PresenceProtocol.

Author: ralphm.
Fixes: #56.

File size: 41.6 KB
Line 
1# Copyright (c) Ralph Meijer.
2# See LICENSE for details
3
4"""
5Tests for L{wokkel.xmppim}.
6"""
7
8from twisted.internet import defer
9from twisted.trial import unittest
10from twisted.words.protocols.jabber import error
11from twisted.words.protocols.jabber.jid import JID
12from twisted.words.protocols.jabber.xmlstream import toResponse
13from twisted.words.xish import domish, utility
14
15from wokkel import xmppim
16from wokkel.generic import ErrorStanza, parseXml
17from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub
18
19NS_XML = 'http://www.w3.org/XML/1998/namespace'
20NS_ROSTER = 'jabber:iq:roster'
21
22class PresenceClientProtocolTest(unittest.TestCase):
23    def setUp(self):
24        self.output = []
25        self.protocol = xmppim.PresenceClientProtocol()
26        self.protocol.parent = self
27
28    def send(self, obj):
29        self.output.append(obj)
30
31    def test_unavailableDirected(self):
32        """
33        Test sending of directed unavailable presence broadcast.
34        """
35
36        self.protocol.unavailable(JID('user@example.com'))
37        presence = self.output[-1]
38        self.assertEquals("presence", presence.name)
39        self.assertEquals(None, presence.uri)
40        self.assertEquals("user@example.com", presence.getAttribute('to'))
41        self.assertEquals("unavailable", presence.getAttribute('type'))
42
43    def test_unavailableWithStatus(self):
44        """
45        Test sending of directed unavailable presence broadcast with status.
46        """
47
48        self.protocol.unavailable(JID('user@example.com'),
49                                  {None: 'Disconnected'})
50        presence = self.output[-1]
51        self.assertEquals("presence", presence.name)
52        self.assertEquals(None, presence.uri)
53        self.assertEquals("user@example.com", presence.getAttribute('to'))
54        self.assertEquals("unavailable", presence.getAttribute('type'))
55        self.assertEquals("Disconnected", unicode(presence.status))
56
57    def test_unavailableBroadcast(self):
58        """
59        Test sending of unavailable presence broadcast.
60        """
61
62        self.protocol.unavailable(None)
63        presence = self.output[-1]
64        self.assertEquals("presence", presence.name)
65        self.assertEquals(None, presence.uri)
66        self.assertEquals(None, presence.getAttribute('to'))
67        self.assertEquals("unavailable", presence.getAttribute('type'))
68
69    def test_unavailableBroadcastNoEntityParameter(self):
70        """
71        Test sending of unavailable presence broadcast by not passing entity.
72        """
73
74        self.protocol.unavailable()
75        presence = self.output[-1]
76        self.assertEquals("presence", presence.name)
77        self.assertEquals(None, presence.uri)
78        self.assertEquals(None, presence.getAttribute('to'))
79        self.assertEquals("unavailable", presence.getAttribute('type'))
80
81
82
83class AvailabilityPresenceTest(unittest.TestCase):
84
85    def test_fromElement(self):
86        xml = """<presence from='user@example.org' to='user@example.com'>
87                   <show>chat</show>
88                   <status>Let's chat!</status>
89                   <priority>50</priority>
90                 </presence>
91              """
92
93        presence = xmppim.AvailabilityPresence.fromElement(parseXml(xml))
94        self.assertEquals(JID('user@example.org'), presence.sender)
95        self.assertEquals(JID('user@example.com'), presence.recipient)
96        self.assertTrue(presence.available)
97        self.assertEquals('chat', presence.show)
98        self.assertEquals({None: "Let's chat!"}, presence.statuses)
99        self.assertEquals(50, presence.priority)
100
101
102class PresenceProtocolTest(unittest.TestCase):
103    """
104    Tests for L{xmppim.PresenceProtocol}
105    """
106
107    def setUp(self):
108        self.output = []
109        self.protocol = xmppim.PresenceProtocol()
110        self.protocol.parent = self
111        self.protocol.xmlstream = utility.EventDispatcher()
112        self.protocol.connectionInitialized()
113
114
115    def send(self, obj):
116        self.output.append(obj)
117
118
119    def test_errorReceived(self):
120        """
121        Incoming presence stanzas are parsed and dispatched.
122        """
123        xml = """<presence type="error"/>"""
124
125        def errorReceived(error):
126            xmppim.PresenceProtocol.errorReceived(self.protocol, error)
127            try:
128                self.assertIsInstance(error, ErrorStanza)
129            except:
130                d.errback()
131            else:
132                d.callback(None)
133
134        d = defer.Deferred()
135        self.protocol.errorReceived = errorReceived
136        self.protocol.xmlstream.dispatch(parseXml(xml))
137        return d
138
139
140    def test_availableReceived(self):
141        """
142        Incoming presence stanzas are parsed and dispatched.
143        """
144        xml = """<presence/>"""
145
146        def availableReceived(presence):
147            xmppim.PresenceProtocol.availableReceived(self.protocol, presence)
148            try:
149                self.assertIsInstance(presence, xmppim.AvailabilityPresence)
150            except:
151                d.errback()
152            else:
153                d.callback(None)
154
155        d = defer.Deferred()
156        self.protocol.availableReceived = availableReceived
157        self.protocol.xmlstream.dispatch(parseXml(xml))
158        return d
159
160
161    def test_unavailableReceived(self):
162        """
163        Incoming presence stanzas are parsed and dispatched.
164        """
165        xml = """<presence type='unavailable'/>"""
166
167        def unavailableReceived(presence):
168            xmppim.PresenceProtocol.unavailableReceived(self.protocol, presence)
169            try:
170                self.assertIsInstance(presence, xmppim.AvailabilityPresence)
171            except:
172                d.errback()
173            else:
174                d.callback(None)
175
176        d = defer.Deferred()
177        self.protocol.unavailableReceived = unavailableReceived
178        self.protocol.xmlstream.dispatch(parseXml(xml))
179        return d
180
181
182    def test_subscribeReceived(self):
183        """
184        Incoming presence stanzas are parsed and dispatched.
185        """
186        xml = """<presence type='subscribe'/>"""
187
188        def subscribeReceived(presence):
189            xmppim.PresenceProtocol.subscribeReceived(self.protocol, presence)
190            try:
191                self.assertIsInstance(presence, xmppim.SubscriptionPresence)
192            except:
193                d.errback()
194            else:
195                d.callback(None)
196
197        d = defer.Deferred()
198        self.protocol.subscribeReceived = subscribeReceived
199        self.protocol.xmlstream.dispatch(parseXml(xml))
200        return d
201
202
203    def test_unsubscribeReceived(self):
204        """
205        Incoming presence stanzas are parsed and dispatched.
206        """
207        xml = """<presence type='unsubscribe'/>"""
208
209        def unsubscribeReceived(presence):
210            xmppim.PresenceProtocol.unsubscribeReceived(self.protocol, presence)
211            try:
212                self.assertIsInstance(presence, xmppim.SubscriptionPresence)
213            except:
214                d.errback()
215            else:
216                d.callback(None)
217
218        d = defer.Deferred()
219        self.protocol.unsubscribeReceived = unsubscribeReceived
220        self.protocol.xmlstream.dispatch(parseXml(xml))
221        return d
222
223
224    def test_subscribedReceived(self):
225        """
226        Incoming presence stanzas are parsed and dispatched.
227        """
228        xml = """<presence type='subscribed'/>"""
229
230        def subscribedReceived(presence):
231            xmppim.PresenceProtocol.subscribedReceived(self.protocol, presence)
232            try:
233                self.assertIsInstance(presence, xmppim.SubscriptionPresence)
234            except:
235                d.errback()
236            else:
237                d.callback(None)
238
239        d = defer.Deferred()
240        self.protocol.subscribedReceived = subscribedReceived
241        self.protocol.xmlstream.dispatch(parseXml(xml))
242        return d
243
244
245    def test_unsubscribedReceived(self):
246        """
247        Incoming presence stanzas are parsed and dispatched.
248        """
249        xml = """<presence type='unsubscribed'/>"""
250
251        def unsubscribedReceived(presence):
252            xmppim.PresenceProtocol.unsubscribedReceived(self.protocol,
253                                                         presence)
254            try:
255                self.assertIsInstance(presence, xmppim.SubscriptionPresence)
256            except:
257                d.errback()
258            else:
259                d.callback(None)
260
261        d = defer.Deferred()
262        self.protocol.unsubscribedReceived = unsubscribedReceived
263        self.protocol.xmlstream.dispatch(parseXml(xml))
264        return d
265
266
267    def test_probeReceived(self):
268        """
269        Incoming presence stanzas are parsed and dispatched.
270        """
271        xml = """<presence type='probe'/>"""
272
273        def probeReceived(presence):
274            xmppim.PresenceProtocol.probeReceived(self.protocol, presence)
275            try:
276                self.assertIsInstance(presence, xmppim.ProbePresence)
277            except:
278                d.errback()
279            else:
280                d.callback(None)
281
282        d = defer.Deferred()
283        self.protocol.probeReceived = probeReceived
284        self.protocol.xmlstream.dispatch(parseXml(xml))
285        return d
286
287    def test_available(self):
288        """
289        It should be possible to pass a sender address.
290        """
291        self.protocol.available(JID('user@example.com'),
292                                show=u'chat',
293                                status=u'Talk to me!',
294                                priority=50)
295        element = self.output[-1]
296        self.assertEquals("user@example.com", element.getAttribute('to'))
297        self.assertIdentical(None, element.getAttribute('type'))
298        self.assertEquals(u'chat', unicode(element.show))
299        self.assertEquals(u'Talk to me!', unicode(element.status))
300        self.assertEquals(u'50', unicode(element.priority))
301
302    def test_availableLanguages(self):
303        """
304        It should be possible to pass a sender address.
305        """
306        self.protocol.available(JID('user@example.com'),
307                                show=u'chat',
308                                statuses={None: u'Talk to me!',
309                                          'nl': u'Praat met me!'},
310                                priority=50)
311        element = self.output[-1]
312        self.assertEquals("user@example.com", element.getAttribute('to'))
313        self.assertIdentical(None, element.getAttribute('type'))
314        self.assertEquals(u'chat', unicode(element.show))
315
316        statuses = {}
317        for status in element.elements():
318            if status.name == 'status':
319                lang = status.getAttribute((NS_XML, 'lang'))
320                statuses[lang] = unicode(status)
321
322        self.assertIn(None, statuses)
323        self.assertEquals(u'Talk to me!', statuses[None])
324        self.assertIn('nl', statuses)
325        self.assertEquals(u'Praat met me!', statuses['nl'])
326        self.assertEquals(u'50', unicode(element.priority))
327
328
329    def test_availableSender(self):
330        """
331        It should be possible to pass a sender address.
332        """
333        self.protocol.available(JID('user@example.com'),
334                                sender=JID('user@example.org'))
335        element = self.output[-1]
336        self.assertEquals("user@example.org", element.getAttribute('from'))
337
338
339    def test_unavailableDirected(self):
340        """
341        Test sending of directed unavailable presence broadcast.
342        """
343
344        self.protocol.unavailable(JID('user@example.com'))
345        element = self.output[-1]
346        self.assertEquals("presence", element.name)
347        self.assertEquals(None, element.uri)
348        self.assertEquals("user@example.com", element.getAttribute('to'))
349        self.assertEquals("unavailable", element.getAttribute('type'))
350
351    def test_unavailableWithStatus(self):
352        """
353        Test sending of directed unavailable presence broadcast with status.
354        """
355
356        self.protocol.unavailable(JID('user@example.com'),
357                                  {None: 'Disconnected'})
358        element = self.output[-1]
359        self.assertEquals("presence", element.name)
360        self.assertEquals(None, element.uri)
361        self.assertEquals("user@example.com", element.getAttribute('to'))
362        self.assertEquals("unavailable", element.getAttribute('type'))
363        self.assertEquals("Disconnected", unicode(element.status))
364
365
366    def test_unavailableBroadcast(self):
367        """
368        Test sending of unavailable presence broadcast.
369        """
370
371        self.protocol.unavailable(None)
372        element = self.output[-1]
373        self.assertEquals("presence", element.name)
374        self.assertEquals(None, element.uri)
375        self.assertEquals(None, element.getAttribute('to'))
376        self.assertEquals("unavailable", element.getAttribute('type'))
377
378
379    def test_unavailableBroadcastNoRecipientParameter(self):
380        """
381        Test sending of unavailable presence broadcast by not passing entity.
382        """
383
384        self.protocol.unavailable()
385        element = self.output[-1]
386        self.assertEquals("presence", element.name)
387        self.assertEquals(None, element.uri)
388        self.assertEquals(None, element.getAttribute('to'))
389        self.assertEquals("unavailable", element.getAttribute('type'))
390
391
392    def test_unavailableSender(self):
393        """
394        It should be possible to pass a sender address.
395        """
396        self.protocol.unavailable(JID('user@example.com'),
397                                  sender=JID('user@example.org'))
398        element = self.output[-1]
399        self.assertEquals("user@example.org", element.getAttribute('from'))
400
401
402    def test_subscribeSender(self):
403        """
404        It should be possible to pass a sender address.
405        """
406        self.protocol.subscribe(JID('user@example.com'),
407                                sender=JID('user@example.org'))
408        element = self.output[-1]
409        self.assertEquals("user@example.org", element.getAttribute('from'))
410
411
412    def test_unsubscribeSender(self):
413        """
414        It should be possible to pass a sender address.
415        """
416        self.protocol.unsubscribe(JID('user@example.com'),
417                                  sender=JID('user@example.org'))
418        element = self.output[-1]
419        self.assertEquals("user@example.org", element.getAttribute('from'))
420
421
422    def test_subscribedSender(self):
423        """
424        It should be possible to pass a sender address.
425        """
426        self.protocol.subscribed(JID('user@example.com'),
427                                 sender=JID('user@example.org'))
428        element = self.output[-1]
429        self.assertEquals("user@example.org", element.getAttribute('from'))
430
431
432    def test_unsubscribedSender(self):
433        """
434        It should be possible to pass a sender address.
435        """
436        self.protocol.unsubscribed(JID('user@example.com'),
437                                   sender=JID('user@example.org'))
438        element = self.output[-1]
439        self.assertEquals("user@example.org", element.getAttribute('from'))
440
441
442    def test_probeSender(self):
443        """
444        It should be possible to pass a sender address.
445        """
446        self.protocol.probe(JID('user@example.com'),
447                            sender=JID('user@example.org'))
448        element = self.output[-1]
449        self.assertEquals("user@example.org", element.getAttribute('from'))
450
451
452
453class RosterItemTest(unittest.TestCase):
454    """
455    Tests for L{xmppim.RosterItem}.
456    """
457
458    def test_toElement(self):
459        """
460        A roster item has the correct namespace/name, lacks unset attributes.
461        """
462        item = xmppim.RosterItem(JID('user@example.org'))
463        element = item.toElement()
464        self.assertEqual('item', element.name)
465        self.assertEqual(NS_ROSTER, element.uri)
466        self.assertFalse(element.hasAttribute('subscription'))
467        self.assertFalse(element.hasAttribute('ask'))
468        self.assertEqual(u"", element.getAttribute('name', u""))
469        self.assertFalse(element.hasAttribute('approved'))
470        self.assertEquals(0, len(list(element.elements())))
471
472
473    def test_toElementMinimal(self):
474        """
475        A bare roster item only has a jid attribute.
476        """
477        item = xmppim.RosterItem(JID('user@example.org'))
478        element = item.toElement()
479        self.assertEqual(u'user@example.org', element.getAttribute('jid'))
480
481
482    def test_toElementSubscriptionNone(self):
483        """
484        A roster item with no subscription has no subscription attribute.
485        """
486        item = xmppim.RosterItem(JID('user@example.org'),
487                                 subscriptionTo=False,
488                                 subscriptionFrom=False)
489        element = item.toElement()
490        self.assertIdentical(None, element.getAttribute('subscription'))
491
492
493    def test_toElementSubscriptionTo(self):
494        """
495        A roster item with subscriptionTo set has subscription 'to'.
496        """
497        item = xmppim.RosterItem(JID('user@example.org'),
498                                 subscriptionTo=True,
499                                 subscriptionFrom=False)
500        element = item.toElement()
501        self.assertEqual('to', element.getAttribute('subscription'))
502
503
504    def test_toElementSubscriptionFrom(self):
505        """
506        A roster item with subscriptionFrom set has subscription 'to'.
507        """
508        item = xmppim.RosterItem(JID('user@example.org'),
509                                 subscriptionTo=False,
510                                 subscriptionFrom=True)
511        element = item.toElement()
512        self.assertEqual('from', element.getAttribute('subscription'))
513
514
515    def test_toElementSubscriptionBoth(self):
516        """
517        A roster item with mutual subscription has subscription 'both'.
518        """
519        item = xmppim.RosterItem(JID('user@example.org'),
520                                 subscriptionTo=True,
521                                 subscriptionFrom=True)
522        element = item.toElement()
523        self.assertEqual('both', element.getAttribute('subscription'))
524
525
526    def test_toElementSubscriptionRemove(self):
527        """
528        A roster item with remove set has subscription 'remove'.
529        """
530        item = xmppim.RosterItem(JID('user@example.org'))
531        item.remove = True
532        element = item.toElement()
533        self.assertEqual('remove', element.getAttribute('subscription'))
534
535
536    def test_toElementAsk(self):
537        """
538        A roster item with pendingOut set has subscription 'ask'.
539        """
540        item = xmppim.RosterItem(JID('user@example.org'))
541        item.pendingOut = True
542        element = item.toElement()
543        self.assertEqual('subscribe', element.getAttribute('ask'))
544
545
546    def test_toElementName(self):
547        """
548        A roster item's name is rendered to the 'name' attribute.
549        """
550        item = xmppim.RosterItem(JID('user@example.org'),
551                                 name='Joe User')
552        element = item.toElement()
553        self.assertEqual(u'Joe User', element.getAttribute('name'))
554
555
556    def test_toElementGroups(self):
557        """
558        A roster item's groups are rendered as 'group' child elements.
559        """
560        groups = set(['Friends', 'Jabber'])
561        item = xmppim.RosterItem(JID('user@example.org'),
562                                 groups=groups)
563
564        element = item.toElement()
565        foundGroups = set()
566        for child in element.elements():
567            if child.uri == NS_ROSTER and child.name == 'group':
568                foundGroups.add(unicode(child))
569
570        self.assertEqual(groups, foundGroups)
571
572
573    def test_toElementApproved(self):
574        """
575        A pre-approved subscription for a roster item has an 'approved' flag.
576        """
577        item = xmppim.RosterItem(JID('user@example.org'))
578        item.approved = True
579        element = item.toElement()
580        self.assertEqual(u'true', element.getAttribute('approved'))
581
582
583    def test_fromElementMinimal(self):
584        """
585        A minimal roster item has a reference to the JID of the contact.
586        """
587
588        xml = """
589            <item xmlns="jabber:iq:roster"
590                  jid="test@example.org"/>
591        """
592
593        item = xmppim.RosterItem.fromElement(parseXml(xml))
594        self.assertEqual(JID(u"test@example.org"), item.entity)
595        self.assertEqual(u"", item.name)
596        self.assertFalse(item.subscriptionTo)
597        self.assertFalse(item.subscriptionFrom)
598        self.assertFalse(item.pendingOut)
599        self.assertFalse(item.approved)
600        self.assertEqual(set(), item.groups)
601
602
603    def test_fromElementName(self):
604        """
605        A roster item may have an optional name.
606        """
607
608        xml = """
609            <item xmlns="jabber:iq:roster"
610                  jid="test@example.org"
611                  name="Test User"/>
612        """
613
614        item = xmppim.RosterItem.fromElement(parseXml(xml))
615        self.assertEqual(u"Test User", item.name)
616
617
618    def test_fromElementGroups(self):
619        """
620        A roster item may have one or more groups.
621        """
622
623        xml = """
624            <item xmlns="jabber:iq:roster"
625                  jid="test@example.org">
626              <group>Friends</group>
627              <group>Twisted</group>
628            </item>
629        """
630
631        item = xmppim.RosterItem.fromElement(parseXml(xml))
632        self.assertIn(u"Twisted", item.groups)
633        self.assertIn(u"Friends", item.groups)
634
635
636    def test_fromElementSubscriptionNone(self):
637        """
638        Subscription 'none' sets both attributes to False.
639        """
640
641        xml = """
642            <item xmlns="jabber:iq:roster"
643                  jid="test@example.org"
644                  subscription="none"/>
645        """
646
647        item = xmppim.RosterItem.fromElement(parseXml(xml))
648        self.assertFalse(item.remove)
649        self.assertFalse(item.subscriptionTo)
650        self.assertFalse(item.subscriptionFrom)
651
652
653    def test_fromElementSubscriptionTo(self):
654        """
655        Subscription 'to' sets the corresponding attribute to True.
656        """
657
658        xml = """
659            <item xmlns="jabber:iq:roster"
660                  jid="test@example.org"
661                  subscription="to"/>
662        """
663
664        item = xmppim.RosterItem.fromElement(parseXml(xml))
665        self.assertFalse(item.remove)
666        self.assertTrue(item.subscriptionTo)
667        self.assertFalse(item.subscriptionFrom)
668
669
670    def test_fromElementSubscriptionFrom(self):
671        """
672        Subscription 'from' sets the corresponding attribute to True.
673        """
674
675        xml = """
676            <item xmlns="jabber:iq:roster"
677                  jid="test@example.org"
678                  subscription="from"/>
679        """
680
681        item = xmppim.RosterItem.fromElement(parseXml(xml))
682        self.assertFalse(item.remove)
683        self.assertFalse(item.subscriptionTo)
684        self.assertTrue(item.subscriptionFrom)
685
686
687    def test_fromElementSubscriptionBoth(self):
688        """
689        Subscription 'both' sets both attributes to True.
690        """
691
692        xml = """
693            <item xmlns="jabber:iq:roster"
694                  jid="test@example.org"
695                  subscription="both"/>
696        """
697
698        item = xmppim.RosterItem.fromElement(parseXml(xml))
699        self.assertFalse(item.remove)
700        self.assertTrue(item.subscriptionTo)
701        self.assertTrue(item.subscriptionFrom)
702
703
704    def test_fromElementSubscriptionRemove(self):
705        """
706        Subscription 'remove' sets the remove attribute.
707        """
708
709        xml = """
710            <item xmlns="jabber:iq:roster"
711                  jid="test@example.org"
712                  subscription="remove"/>
713        """
714
715        item = xmppim.RosterItem.fromElement(parseXml(xml))
716        self.assertTrue(item.remove)
717
718
719    def test_fromElementPendingOut(self):
720        """
721        The ask attribute, if set to 'subscription', means pending out.
722        """
723
724        xml = """
725            <item xmlns="jabber:iq:roster"
726                  jid="test@example.org"
727                  ask="subscribe"/>
728        """
729
730        item = xmppim.RosterItem.fromElement(parseXml(xml))
731        self.assertTrue(item.pendingOut)
732
733
734    def test_fromElementApprovedTrue(self):
735        """
736        The approved attribute (true) signals a pre-approved subscription.
737        """
738
739        xml = """
740            <item xmlns="jabber:iq:roster"
741                  jid="test@example.org"
742                  approved="true"/>
743        """
744
745        item = xmppim.RosterItem.fromElement(parseXml(xml))
746        self.assertTrue(item.approved)
747
748
749    def test_fromElementApproved1(self):
750        """
751        The approved attribute (1) signals a pre-approved subscription.
752        """
753
754        xml = """
755            <item xmlns="jabber:iq:roster"
756                  jid="test@example.org"
757                  approved="1"/>
758        """
759
760        item = xmppim.RosterItem.fromElement(parseXml(xml))
761        self.assertTrue(item.approved)
762
763
764    def test_jidDeprecationGet(self):
765        """
766        Getting the jid attribute works as entity and warns deprecation.
767        """
768        item = xmppim.RosterItem(JID('user@example.org'))
769        entity = self.assertWarns(DeprecationWarning,
770                                  "wokkel.xmppim.RosterItem.jid is deprecated. "
771                                  "Use RosterItem.entity instead.",
772                                  xmppim.__file__,
773                                  getattr, item, 'jid')
774        self.assertIdentical(entity, item.entity)
775
776
777    def test_jidDeprecationSet(self):
778        """
779        Setting the jid attribute works as entity and warns deprecation.
780        """
781        item = xmppim.RosterItem(JID('user@example.org'))
782        self.assertWarns(DeprecationWarning,
783                         "wokkel.xmppim.RosterItem.jid is deprecated. "
784                         "Use RosterItem.entity instead.",
785                         xmppim.__file__,
786                         setattr, item, 'jid',
787                         JID('other@example.org'))
788        self.assertEqual(JID('other@example.org'), item.entity)
789
790
791    def test_askDeprecationGet(self):
792        """
793        Getting the ask attribute works as entity and warns deprecation.
794        """
795        item = xmppim.RosterItem(JID('user@example.org'))
796        item.pendingOut = True
797        ask = self.assertWarns(DeprecationWarning,
798                               "wokkel.xmppim.RosterItem.ask is deprecated. "
799                               "Use RosterItem.pendingOut instead.",
800                               xmppim.__file__,
801                               getattr, item, 'ask')
802        self.assertTrue(ask)
803
804
805    def test_askDeprecationSet(self):
806        """
807        Setting the ask attribute works as entity and warns deprecation.
808        """
809        item = xmppim.RosterItem(JID('user@example.org'))
810        self.assertWarns(DeprecationWarning,
811                         "wokkel.xmppim.RosterItem.ask is deprecated. "
812                         "Use RosterItem.pendingOut instead.",
813                         xmppim.__file__,
814                         setattr, item, 'ask',
815                         True)
816        self.assertTrue(item.pendingOut)
817
818
819
820class RosterRequestTest(unittest.TestCase):
821    """
822    Tests for L{xmppim.RosterRequest}.
823    """
824
825    def test_fromElement(self):
826        """
827        A bare roster request is parsed and missing information is None.
828        """
829        xml = """
830            <iq type='get' to='this@example.org/Home' from='this@example.org'>
831              <query xmlns='jabber:iq:roster'/>
832            </iq>
833        """
834
835        request = xmppim.RosterRequest.fromElement(parseXml(xml))
836        self.assertEqual('get', request.stanzaType)
837        self.assertEqual(JID('this@example.org/Home'), request.recipient)
838        self.assertEqual(JID('this@example.org'), request.sender)
839        self.assertEqual(None, request.item)
840        self.assertEqual(None, request.version)
841
842
843    def test_fromElementItem(self):
844        """
845        If an item is present, parse it and put it in the request item.
846        """
847        xml = """
848            <iq type='set' to='this@example.org/Home' from='this@example.org'>
849              <query xmlns='jabber:iq:roster'>
850                <item jid='user@example.org'/>
851              </query>
852            </iq>
853        """
854
855        request = xmppim.RosterRequest.fromElement(parseXml(xml))
856        self.assertNotIdentical(None, request.item)
857        self.assertEqual(JID('user@example.org'), request.item.entity)
858
859
860    def test_fromElementVersion(self):
861        """
862        If a ver attribute is present, put it in the request version.
863        """
864        xml = """
865            <iq type='set' to='this@example.org/Home' from='this@example.org'>
866              <query xmlns='jabber:iq:roster' ver='ver72'>
867                <item jid='user@example.org'/>
868              </query>
869            </iq>
870        """
871        request = xmppim.RosterRequest.fromElement(parseXml(xml))
872        self.assertEqual('ver72', request.version)
873
874
875    def test_fromElementVersionEmpty(self):
876        """
877        The ver attribute may be empty.
878        """
879        xml = """
880            <iq type='get' to='this@example.org/Home' from='this@example.org'>
881              <query xmlns='jabber:iq:roster' ver=''/>
882            </iq>
883        """
884        request = xmppim.RosterRequest.fromElement(parseXml(xml))
885        self.assertEqual('', request.version)
886
887
888    def test_toElement(self):
889        """
890        A roster request has a query element in the roster namespace.
891        """
892        request = xmppim.RosterRequest()
893        element = request.toElement()
894        children = element.elements()
895        child = children.next()
896        self.assertEqual(NS_ROSTER, child.uri)
897        self.assertEqual('query', child.name)
898
899
900    def test_toElementItem(self):
901        """
902        If an item is set, it is rendered as a child of the query.
903        """
904        request = xmppim.RosterRequest()
905        request.item = xmppim.RosterItem(JID('user@example.org'))
906        element = request.toElement()
907        children = element.query.elements()
908        child = children.next()
909        self.assertEqual(NS_ROSTER, child.uri)
910        self.assertEqual('item', child.name)
911
912
913
914class FakeClient(object):
915    """
916    Fake client stream manager for roster tests.
917    """
918
919    def __init__(self, xmlstream, jid):
920        self.xmlstream = xmlstream
921        self.jid = jid
922
923
924    def request(self, request):
925        element = request.toElement()
926        self.xmlstream.send(element)
927        return defer.Deferred()
928
929
930    def addHandler(self, handler):
931        handler.makeConnection(self.xmlstream)
932        handler.connectionInitialized()
933
934
935    def test_toElementVersion(self):
936        """
937        If the roster version is set, a 'ver' attribute is added.
938        """
939        request = xmppim.RosterRequest()
940        request.version = 'ver72'
941        element = request.toElement()
942        self.assertEqual('ver72', element.query.getAttribute('ver'))
943
944
945    def test_toElementVersionEmpty(self):
946        """
947        If the roster version is the empty string, it should add 'ver', too.
948        """
949        request = xmppim.RosterRequest()
950        request.version = ''
951        element = request.toElement()
952        self.assertEqual('', element.query.getAttribute('ver'))
953
954
955
956class RosterClientProtocolTest(unittest.TestCase, TestableRequestHandlerMixin):
957    """
958    Tests for L{xmppim.RosterClientProtocol}.
959    """
960
961    def setUp(self):
962        self.stub = XmlStreamStub()
963        self.client = FakeClient(self.stub.xmlstream, JID('this@example.org'))
964        self.service = xmppim.RosterClientProtocol()
965        self.service.setHandlerParent(self.client)
966
967
968    def test_setItem(self):
969        """
970        Setting a roster item renders the item and sends it out.
971        """
972        item = xmppim.RosterItem(JID('test@example.org'),
973                                 name='Joe User',
974                                 groups=set(['Friends', 'Jabber']))
975        d = self.service.setItem(item)
976
977        # Inspect outgoing iq request
978
979        iq = self.stub.output[-1]
980        self.assertEqual('set', iq.getAttribute('type'))
981        self.assertNotIdentical(None, iq.query)
982        self.assertEqual(NS_ROSTER, iq.query.uri)
983
984        children = list(domish.generateElementsQNamed(iq.query.children,
985                                                      'item', NS_ROSTER))
986        self.assertEqual(1, len(children))
987        child = children[0]
988        self.assertEqual('test@example.org', child['jid'])
989        self.assertIdentical(None, child.getAttribute('subscription'))
990
991        # Fake successful response
992
993        response = toResponse(iq, 'result')
994        d.callback(response)
995        return d
996
997
998    def test_setItemIgnoreAttributes(self):
999        """
1000        Certain attributes should be rendered for roster set.
1001        """
1002        item = xmppim.RosterItem(JID('test@example.org'),
1003                                 subscriptionTo=True,
1004                                 subscriptionFrom=False,
1005                                 name='Joe User',
1006                                 groups=set(['Friends', 'Jabber']))
1007        item.pendingOut = True
1008        item.approved = True
1009        d = self.service.setItem(item)
1010
1011        # Inspect outgoing iq request
1012
1013        iq = self.stub.output[-1]
1014        self.assertEqual('set', iq.getAttribute('type'))
1015        self.assertNotIdentical(None, iq.query)
1016        self.assertEqual(NS_ROSTER, iq.query.uri)
1017
1018        children = list(domish.generateElementsQNamed(iq.query.children,
1019                                                      'item', NS_ROSTER))
1020        self.assertEqual(1, len(children))
1021        child = children[0]
1022        self.assertIdentical(None, child.getAttribute('ask'))
1023        self.assertIdentical(None, child.getAttribute('approved'))
1024        self.assertIdentical(None, child.getAttribute('subscription'))
1025
1026        # Fake successful response
1027
1028        response = toResponse(iq, 'result')
1029        d.callback(response)
1030        return d
1031
1032
1033    def test_removeItem(self):
1034        """
1035        Removing a roster item is setting an item with subscription C{remove}.
1036        """
1037        d = self.service.removeItem(JID('test@example.org'))
1038
1039        # Inspect outgoing iq request
1040
1041        iq = self.stub.output[-1]
1042        self.assertEqual('set', iq.getAttribute('type'))
1043        self.assertNotIdentical(None, iq.query)
1044        self.assertEqual(NS_ROSTER, iq.query.uri)
1045
1046        children = list(domish.generateElementsQNamed(iq.query.children,
1047                                                      'item', NS_ROSTER))
1048        self.assertEqual(1, len(children))
1049        child = children[0]
1050        self.assertEqual('test@example.org', child['jid'])
1051        self.assertEqual('remove', child.getAttribute('subscription'))
1052
1053        # Fake successful response
1054
1055        response = toResponse(iq, 'result')
1056        d.callback(response)
1057        return d
1058
1059
1060    def test_getRoster(self):
1061        """
1062        A request for the roster is sent out and the response is parsed.
1063        """
1064        def cb(roster):
1065            self.assertIn(JID('user@example.org'), roster)
1066            self.assertIdentical(None, getattr(roster, 'version'))
1067
1068        d = self.service.getRoster()
1069        d.addCallback(cb)
1070
1071        # Inspect outgoing iq request
1072
1073        iq = self.stub.output[-1]
1074        self.assertEqual('get', iq.getAttribute('type'))
1075        self.assertNotIdentical(None, iq.query)
1076        self.assertEqual(NS_ROSTER, iq.query.uri)
1077        self.assertFalse(iq.query.hasAttribute('ver'))
1078
1079        # Fake successful response
1080        response = toResponse(iq, 'result')
1081        query = response.addElement((NS_ROSTER, 'query'))
1082        item = query.addElement('item')
1083        item['jid'] = 'user@example.org'
1084
1085        d.callback(response)
1086        return d
1087
1088
1089    def test_getRosterVer(self):
1090        """
1091        A request for the roster with version passes the version on.
1092        """
1093        def cb(roster):
1094            self.assertEqual('ver96', getattr(roster, 'version'))
1095
1096        d = self.service.getRoster(version='ver72')
1097        d.addCallback(cb)
1098
1099        # Inspect outgoing iq request
1100
1101        iq = self.stub.output[-1]
1102        self.assertEqual('ver72', iq.query.getAttribute('ver'))
1103
1104        # Fake successful response
1105        response = toResponse(iq, 'result')
1106        query = response.addElement((NS_ROSTER, 'query'))
1107        query['ver'] = 'ver96'
1108        item = query.addElement('item')
1109        item['jid'] = 'user@example.org'
1110
1111        d.callback(response)
1112        return d
1113
1114
1115    def test_getRosterVerEmptyResult(self):
1116        """
1117        An empty response is returned as None.
1118        """
1119        def cb(response):
1120            self.assertIdentical(None, response)
1121
1122        d = self.service.getRoster(version='ver72')
1123        d.addCallback(cb)
1124
1125        # Inspect outgoing iq request
1126
1127        iq = self.stub.output[-1]
1128
1129        # Fake successful response
1130        response = toResponse(iq, 'result')
1131        d.callback(response)
1132        return d
1133
1134
1135    def test_onRosterSet(self):
1136        """
1137        A roster push causes onRosterSet to be called with the parsed item.
1138        """
1139        xml = """
1140          <iq type='set'>
1141            <query xmlns='jabber:iq:roster'>
1142              <item jid='user@example.org'/>
1143            </query>
1144          </iq>
1145        """
1146
1147        items = []
1148
1149        def onRosterSet(item):
1150            items.append(item)
1151
1152        def cb(result):
1153            self.assertEqual(1, len(items))
1154            self.assertEqual(JID('user@example.org'), items[0].entity)
1155
1156        self.service.onRosterSet = onRosterSet
1157
1158        d = self.assertWarns(DeprecationWarning,
1159                             "wokkel.xmppim.RosterClientProtocol.onRosterSet "
1160                             "is deprecated. "
1161                             "Use RosterClientProtocol.setReceived instead.",
1162                             xmppim.__file__,
1163                             self.handleRequest, xml)
1164        d.addCallback(cb)
1165        return d
1166
1167
1168    def test_onRosterRemove(self):
1169        """
1170        A roster push causes onRosterSet to be called with the parsed item.
1171        """
1172        xml = """
1173          <iq type='set'>
1174            <query xmlns='jabber:iq:roster'>
1175              <item jid='user@example.org' subscription='remove'/>
1176            </query>
1177          </iq>
1178        """
1179
1180        entities = []
1181
1182        def onRosterRemove(entity):
1183            entities.append(entity)
1184
1185        def cb(result):
1186            self.assertEqual([JID('user@example.org')], entities)
1187
1188        self.service.onRosterRemove = onRosterRemove
1189
1190        d = self.assertWarns(DeprecationWarning,
1191                             "wokkel.xmppim.RosterClientProtocol.onRosterRemove "
1192                             "is deprecated. "
1193                             "Use RosterClientProtocol.removeReceived instead.",
1194                             xmppim.__file__,
1195                             self.handleRequest, xml)
1196        d.addCallback(cb)
1197        return d
1198
1199
1200    def test_setReceived(self):
1201        """
1202        A roster set push causes setReceived.
1203        """
1204        xml = """
1205          <iq type='set'>
1206            <query xmlns='jabber:iq:roster'>
1207              <item jid='user@example.org'/>
1208            </query>
1209          </iq>
1210        """
1211
1212        requests = []
1213
1214        def setReceived(request):
1215            requests.append(request)
1216
1217        def cb(result):
1218            self.assertEqual(1, len(requests), "setReceived was not called")
1219            self.assertEqual(JID('user@example.org'), requests[0].item.entity)
1220
1221        self.service.setReceived = setReceived
1222
1223        d = self.handleRequest(xml)
1224        d.addCallback(cb)
1225        return d
1226
1227
1228    def test_setReceivedOtherSource(self):
1229        """
1230        Roster pushes can be sent from other entities, too, ignore them.
1231        """
1232        xml = """
1233          <iq type='set' to='this@example.org/Home' from='other@example.org'>
1234            <query xmlns='jabber:iq:roster'>
1235              <item jid='user@example.org'/>
1236            </query>
1237          </iq>
1238        """
1239
1240        def cb(result):
1241            self.assertEquals('service-unavailable', result.condition)
1242
1243        d = self.handleRequest(xml)
1244        self.assertFailure(d, error.StanzaError)
1245        d.addCallback(cb)
1246        return d
1247
1248
1249    def test_setReceivedOtherSourceAllowed(self):
1250        """
1251        Roster pushes can be sent from other entities, allow them.
1252        """
1253        xml = """
1254          <iq type='set' to='this@example.org/Home' from='other@example.org'>
1255            <query xmlns='jabber:iq:roster'>
1256              <item jid='user@example.org'/>
1257            </query>
1258          </iq>
1259        """
1260
1261        self.service.allowAnySender = True
1262        requests = []
1263
1264        def setReceived(request):
1265            requests.append(request)
1266
1267        def cb(result):
1268            self.assertEqual(1, len(requests), "setReceived was not called")
1269
1270        self.service.setReceived = setReceived
1271
1272        d = self.handleRequest(xml)
1273        d.addCallback(cb)
1274        return d
1275
1276
1277    def test_setReceivedOtherSourceIgnored(self):
1278        """
1279        Roster pushes can be sent from other entities, allow them.
1280        """
1281        xml = """
1282          <iq type='set' to='this@example.org/Home' from='bad@example.org'>
1283            <query xmlns='jabber:iq:roster'>
1284              <item jid='user@example.org'/>
1285            </query>
1286          </iq>
1287        """
1288
1289        self.service.allowAnySender = True
1290
1291        def setReceived(request):
1292            if request.sender == JID('bad@example.org'):
1293                raise xmppim.RosterPushIgnored()
1294
1295        def cb(result):
1296            self.assertEquals('service-unavailable', result.condition)
1297
1298        self.service.setReceived = setReceived
1299
1300
1301        d = self.handleRequest(xml)
1302        self.assertFailure(d, error.StanzaError)
1303        d.addCallback(cb)
1304        return d
1305
1306
1307    def test_removeReceived(self):
1308        """
1309        A roster remove push causes removeReceived.
1310        """
1311        xml = """
1312          <iq type='set'>
1313            <query xmlns='jabber:iq:roster'>
1314              <item jid='user@example.org' subscription='remove'/>
1315            </query>
1316          </iq>
1317        """
1318
1319        requests = []
1320
1321        def removeReceived(request):
1322            requests.append(request)
1323
1324        def cb(result):
1325            self.assertEqual(1, len(requests), "removeReceived was not called")
1326            self.assertEqual(JID('user@example.org'), requests[0].item.entity)
1327
1328        self.service.removeReceived = removeReceived
1329
1330        d = self.handleRequest(xml)
1331        d.addCallback(cb)
1332        return d
Note: See TracBrowser for help on using the repository browser.