source: wokkel/test/test_muc.py @ 204:c13df6bb0135

Last change on this file since 204:c13df6bb0135 was 204:c13df6bb0135, checked in by Ralph Meijer <ralphm@…>, 5 years ago

imported patch py3-muc.patch

File size: 61.1 KB
Line 
1# Copyright (c) Ralph Meijer.
2# See LICENSE for details.
3
4"""
5Tests for L{wokkel.muc}
6"""
7
8from __future__ import division, absolute_import
9
10from datetime import datetime
11from dateutil.tz import tzutc
12
13from zope.interface import verify
14
15from twisted.trial import unittest
16from twisted.internet import defer, task
17from twisted.python.compat import iteritems, unicode
18from twisted.words.xish import domish, xpath
19from twisted.words.protocols.jabber.jid import JID
20from twisted.words.protocols.jabber.error import StanzaError
21from twisted.words.protocols.jabber.xmlstream import TimeoutError, toResponse
22
23from wokkel import data_form, delay, iwokkel, muc
24from wokkel.generic import parseXml
25from wokkel.test.helpers import TestableStreamManager
26
27
28NS_MUC_ADMIN = 'http://jabber.org/protocol/muc#admin'
29
30def calledAsync(fn):
31    """
32    Function wrapper that fires a deferred upon calling the given function.
33    """
34    d = defer.Deferred()
35
36    def func(*args, **kwargs):
37        try:
38            result = fn(*args, **kwargs)
39        except:
40            d.errback()
41        else:
42            d.callback(result)
43
44    return d, func
45
46
47
48class StatusCodeTest(unittest.TestCase):
49    """
50    Tests for L{muc.STATUS_CODE}.
51    """
52
53    def test_lookupByValue(self):
54        """
55        The registered MUC status codes map to STATUS_CODE value constants.
56
57        Note: the identifiers used in the dictionary of status codes are
58        borrowed from U{XEP-0306<http://xmpp.org/extensions/xep-0306.html>}
59        that defines Extensible Status Conditions for Multi-User Chat. If this
60        specification is implemented itself, the dictionary could move there.
61        """
62        codes = {
63            100: 'realjid-public',
64            101: 'affiliation-changed',
65            102: 'unavailable-shown',
66            103: 'unavailable-not-shown',
67            104: 'configuration-changed',
68            110: 'self-presence',
69            170: 'logging-enabled',
70            171: 'logging-disabled',
71            172: 'non-anonymous',
72            173: 'semi-anonymous',
73            174: 'fully-anonymous',
74            201: 'room-created',
75            210: 'nick-assigned',
76            301: 'banned',
77            303: 'new-nick',
78            307: 'kicked',
79            321: 'removed-affiliation',
80            322: 'removed-membership',
81            332: 'removed-shutdown',
82        }
83
84        for code, condition in iteritems(codes):
85            constantName = condition.replace('-', '_').upper()
86            self.assertEqual(getattr(muc.STATUS_CODE, constantName),
87                             muc.STATUS_CODE.lookupByValue(code))
88
89
90
91class StatusesTest(unittest.TestCase):
92    """
93    Tests for L{muc.Statuses}.
94    """
95
96    def setUp(self):
97        self.mucStatuses = muc.Statuses()
98        self.mucStatuses.add(muc.STATUS_CODE.SELF_PRESENCE)
99        self.mucStatuses.add(muc.STATUS_CODE.ROOM_CREATED)
100
101
102    def test_interface(self):
103        """
104        Instances of L{Statuses} provide L{iwokkel.IMUCStatuses}.
105        """
106        verify.verifyObject(iwokkel.IMUCStatuses, self.mucStatuses)
107
108
109    def test_contains(self):
110        """
111        The status contained are 'in' the container.
112        """
113        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, self.mucStatuses)
114        self.assertIn(muc.STATUS_CODE.ROOM_CREATED, self.mucStatuses)
115        self.assertNotIn(muc.STATUS_CODE.NON_ANONYMOUS, self.mucStatuses)
116
117
118    def test_iter(self):
119        """
120        All statuses can be iterated over.
121        """
122        statuses = set()
123        for status in self.mucStatuses:
124            statuses.add(status)
125
126        self.assertEqual(set([muc.STATUS_CODE.SELF_PRESENCE,
127                              muc.STATUS_CODE.ROOM_CREATED]), statuses)
128
129
130    def test_len(self):
131        """
132        The number of items in this container is returned by C{__len__}.
133        """
134        self.assertEqual(2, len(self.mucStatuses))
135
136
137
138class GroupChatTest(unittest.TestCase):
139    """
140    Tests for L{muc.GroupChat}.
141    """
142
143
144    def test_toElementDelay(self):
145        """
146        If the delay attribute is set, toElement has it rendered.
147        """
148        message = muc.GroupChat()
149        message.delay = delay.Delay(stamp=datetime(2002, 10, 13, 23, 58, 37,
150                                                   tzinfo=tzutc()))
151
152        element = message.toElement()
153
154        query = "/message/delay[@xmlns='%s']" % (delay.NS_DELAY,)
155        nodes = xpath.queryForNodes(query, element)
156        self.assertNotIdentical(None, nodes, "Missing delay element")
157
158
159    def test_toElementDelayLegacy(self):
160        """
161        If legacy delay is requested, the legacy format is rendered.
162        """
163        message = muc.GroupChat()
164        message.delay = delay.Delay(stamp=datetime(2002, 10, 13, 23, 58, 37,
165                                                   tzinfo=tzutc()))
166
167        element = message.toElement(legacyDelay=True)
168
169        query = "/message/x[@xmlns='%s']" % (delay.NS_JABBER_DELAY,)
170        nodes = xpath.queryForNodes(query, element)
171        self.assertNotIdentical(None, nodes, "Missing legacy delay element")
172
173
174
175class HistoryOptionsTest(unittest.TestCase):
176    """
177    Tests for L{muc.HistoryOptionsTest}.
178    """
179
180    def test_toElement(self):
181        """
182        toElement renders the history element in the right namespace.
183        """
184        history = muc.HistoryOptions()
185
186        element = history.toElement()
187
188        self.assertEqual(muc.NS_MUC, element.uri)
189        self.assertEqual('history', element.name)
190
191
192    def test_toElementMaxStanzas(self):
193        """
194        If C{maxStanzas} is set, the element has the attribute C{'maxstanzas'}.
195        """
196        history = muc.HistoryOptions(maxStanzas=10)
197
198        element = history.toElement()
199
200        self.assertEqual(u'10', element.getAttribute('maxstanzas'))
201
202
203    def test_toElementSince(self):
204        """
205        If C{since} is set, the attribute C{'since'} has a rendered timestamp.
206        """
207        history = muc.HistoryOptions(since=datetime(2002, 10, 13, 23, 58, 37,
208                                                   tzinfo=tzutc()))
209
210        element = history.toElement()
211
212        self.assertEqual(u'2002-10-13T23:58:37Z',
213                         element.getAttribute('since'))
214
215
216
217class UserPresenceTest(unittest.TestCase):
218    """
219    Tests for L{muc.UserPresence}.
220    """
221
222    def test_fromElementNoUserElement(self):
223        """
224        Without user element, all associated attributes are None.
225        """
226        xml = """
227            <presence from='coven@chat.shakespeare.lit/thirdwitch'
228                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
229                      to='hag66@shakespeare.lit/pda'>
230            </presence>
231        """
232
233        element = parseXml(xml)
234        presence = muc.UserPresence.fromElement(element)
235
236        self.assertIdentical(None, presence.affiliation)
237        self.assertIdentical(None, presence.role)
238        self.assertIdentical(None, presence.entity)
239        self.assertIdentical(None, presence.nick)
240        self.assertEqual(0, len(presence.mucStatuses))
241
242
243    def test_fromElementUnknownChild(self):
244        """
245        Unknown child elements are ignored.
246        """
247        xml = """
248            <presence from='coven@chat.shakespeare.lit/thirdwitch'
249                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
250                      to='hag66@shakespeare.lit/pda'>
251              <x xmlns='http://jabber.org/protocol/muc#user'>
252                <status xmlns='myns' code='110'/>
253              </x>
254            </presence>
255        """
256
257        element = parseXml(xml)
258        presence = muc.UserPresence.fromElement(element)
259
260        self.assertEqual(0, len(presence.mucStatuses))
261
262
263    def test_fromElementStatusOne(self):
264        """
265        Status codes are extracted.
266        """
267        xml = """
268            <presence from='coven@chat.shakespeare.lit/thirdwitch'
269                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
270                      to='hag66@shakespeare.lit/pda'>
271              <x xmlns='http://jabber.org/protocol/muc#user'>
272                <item affiliation='member' role='participant'/>
273                <status code='110'/>
274              </x>
275            </presence>
276        """
277
278        element = parseXml(xml)
279        presence = muc.UserPresence.fromElement(element)
280
281        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, presence.mucStatuses)
282
283
284    def test_fromElementStatusMultiple(self):
285        """
286        Multiple status codes are all extracted.
287        """
288        xml = """
289            <presence from='coven@chat.shakespeare.lit/thirdwitch'
290                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
291                      to='hag66@shakespeare.lit/pda'>
292              <x xmlns='http://jabber.org/protocol/muc#user'>
293                <item affiliation='member' role='participant'/>
294                <status code='100'/>
295                <status code='110'/>
296              </x>
297            </presence>
298        """
299
300        element = parseXml(xml)
301        presence = muc.UserPresence.fromElement(element)
302
303        self.assertIn(muc.STATUS_CODE.SELF_PRESENCE, presence.mucStatuses)
304        self.assertIn(muc.STATUS_CODE.REALJID_PUBLIC, presence.mucStatuses)
305
306
307    def test_fromElementStatusEmpty(self):
308        """
309        Empty status elements are ignored.
310        """
311        xml = """
312            <presence from='coven@chat.shakespeare.lit/thirdwitch'
313                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
314                      to='hag66@shakespeare.lit/pda'>
315              <x xmlns='http://jabber.org/protocol/muc#user'>
316                <item affiliation='member' role='participant'/>
317                <status/>
318              </x>
319            </presence>
320        """
321
322        element = parseXml(xml)
323        presence = muc.UserPresence.fromElement(element)
324
325        self.assertEqual(0, len(presence.mucStatuses))
326
327
328    def test_fromElementStatusBad(self):
329        """
330        Bad status codes are ignored.
331        """
332        xml = """
333            <presence from='coven@chat.shakespeare.lit/thirdwitch'
334                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
335                      to='hag66@shakespeare.lit/pda'>
336              <x xmlns='http://jabber.org/protocol/muc#user'>
337                <item affiliation='member' role='participant'/>
338                <status code="badvalue"/>
339              </x>
340            </presence>
341        """
342
343        element = parseXml(xml)
344        presence = muc.UserPresence.fromElement(element)
345
346        self.assertEqual(0, len(presence.mucStatuses))
347
348
349    def test_fromElementStatusUnknown(self):
350        """
351        Unknown status codes are not recorded in C{mucStatuses}.
352        """
353        xml = """
354            <presence from='coven@chat.shakespeare.lit/thirdwitch'
355                      id='026B3509-2CCE-4D69-96D6-25F41FFDC408'
356                      to='hag66@shakespeare.lit/pda'>
357              <x xmlns='http://jabber.org/protocol/muc#user'>
358                <item affiliation='member' role='participant'/>
359                <status code="999"/>
360              </x>
361            </presence>
362        """
363
364        element = parseXml(xml)
365        presence = muc.UserPresence.fromElement(element)
366
367        self.assertEqual(0, len(presence.mucStatuses))
368
369
370    def test_fromElementItem(self):
371        """
372        Item attributes are parsed properly.
373        """
374        xml = """
375            <presence from='coven@chat.shakespeare.lit/thirdwitch'
376                      to='crone1@shakespeare.lit/desktop'>
377              <x xmlns='http://jabber.org/protocol/muc#user'>
378                <item affiliation='member'
379                      jid='hag66@shakespeare.lit/pda'
380                      role='participant'
381                      nick='thirdwitch'/>
382              </x>
383            </presence>
384        """
385
386        element = parseXml(xml)
387        presence = muc.UserPresence.fromElement(element)
388        self.assertEqual(u'member', presence.affiliation)
389        self.assertEqual(u'participant', presence.role)
390        self.assertEqual(JID('hag66@shakespeare.lit/pda'), presence.entity)
391        self.assertEqual(u'thirdwitch', presence.nick)
392
393
394
395class MUCClientProtocolTest(unittest.TestCase):
396    """
397    Tests for L{muc.MUCClientProtocol}.
398    """
399
400    def setUp(self):
401        self.clock = task.Clock()
402        self.sessionManager = TestableStreamManager(reactor=self.clock)
403        self.stub = self.sessionManager.stub
404        self.protocol = muc.MUCClientProtocol(reactor=self.clock)
405        self.protocol.setHandlerParent(self.sessionManager)
406
407        self.roomIdentifier = 'test'
408        self.service  = 'conference.example.org'
409        self.nick = 'Nick'
410
411        self.occupantJID = JID(tuple=(self.roomIdentifier,
412                                      self.service,
413                                      self.nick))
414        self.roomJID = self.occupantJID.userhostJID()
415        self.userJID = JID('test@example.org/Testing')
416
417
418    def test_initNoReactor(self):
419        """
420        If no reactor is passed, the default reactor is used.
421        """
422        protocol = muc.MUCClientProtocol()
423        from twisted.internet import reactor
424        self.assertEqual(reactor, protocol._reactor)
425
426
427    def test_groupChatReceived(self):
428        """
429        Messages of type groupchat are parsed and passed to L{groupChatReceived}.
430        """
431        xml = u"""
432            <message to='test@test.com' from='%s' type='groupchat'>
433              <body>test</body>
434            </message>
435        """ % (self.occupantJID)
436
437        def groupChatReceived(message):
438            self.assertEquals('test', message.body, "Wrong group chat message")
439            self.assertEquals(self.roomIdentifier, message.sender.user,
440                              'Wrong room identifier')
441
442        d, self.protocol.groupChatReceived = calledAsync(groupChatReceived)
443        self.stub.send(parseXml(xml))
444        return d
445
446
447    def test_groupChatReceivedNotOverridden(self):
448        """
449        If L{groupChatReceived} has not been overridden, no errors should occur.
450        """
451        xml = u"""
452            <message to='test@test.com' from='%s' type='groupchat'>
453              <body>test</body>
454            </message>
455        """ % (self.occupantJID)
456
457        self.stub.send(parseXml(xml))
458
459
460    def test_join(self):
461        """
462        Joining a room waits for confirmation, deferred fires user presence.
463        """
464
465        def cb(presence):
466            self.assertEquals(self.occupantJID, presence.sender)
467
468        # Join the room
469        d = self.protocol.join(self.roomJID, self.nick)
470        d.addCallback(cb)
471
472        element = self.stub.output[-1]
473
474        self.assertEquals('presence', element.name, "Need to be presence")
475        self.assertNotIdentical(None, element.x, 'No muc x element')
476
477        # send back user presence, they joined
478        xml = """
479            <presence from='%s@%s/%s'>
480              <x xmlns='http://jabber.org/protocol/muc#user'>
481                <item affiliation='member' role='participant'/>
482              </x>
483            </presence>
484        """ % (self.roomIdentifier, self.service, self.nick)
485        self.stub.send(parseXml(xml))
486        return d
487
488
489    def test_joinHistory(self):
490        """
491        Passing a history parameter sends a 'maxStanzas' history limit.
492        """
493
494        historyOptions = muc.HistoryOptions(maxStanzas=10)
495        d = self.protocol.join(self.roomJID, self.nick,
496                               historyOptions)
497
498        element = self.stub.output[-1]
499        query = "/*/x[@xmlns='%s']/history[@xmlns='%s']" % (muc.NS_MUC,
500                                                            muc.NS_MUC)
501        result = xpath.queryForNodes(query, element)
502        history = result[0]
503        self.assertEquals('10', history.getAttribute('maxstanzas'))
504
505        # send back user presence, they joined
506        xml = """
507            <presence from='%s@%s/%s'>
508              <x xmlns='http://jabber.org/protocol/muc#user'>
509                <item affiliation='member' role='participant'/>
510              </x>
511            </presence>
512        """ % (self.roomIdentifier, self.service, self.nick)
513        self.stub.send(parseXml(xml))
514        return d
515
516
517    def test_joinForbidden(self):
518        """
519        A forbidden error in response to a join errbacks with L{StanzaError}.
520        """
521
522        def cb(error):
523            self.assertEquals('forbidden', error.condition,
524                              'Wrong muc condition')
525
526        d = self.protocol.join(self.roomJID, self.nick)
527        self.assertFailure(d, StanzaError)
528        d.addCallback(cb)
529
530        # send back error, forbidden
531        xml = u"""
532            <presence from='%s' type='error'>
533              <error type='auth'>
534                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
535              </error>
536            </presence>
537        """ % (self.occupantJID)
538        self.stub.send(parseXml(xml))
539        return d
540
541
542    def test_joinForbiddenFromRoomJID(self):
543        """
544        An error response to a join sent from the room JID should errback.
545
546        Some service implementations send error stanzas from the room JID
547        instead of the JID the join presence was sent to.
548        """
549
550        d = self.protocol.join(self.roomJID, self.nick)
551        self.assertFailure(d, StanzaError)
552
553        # send back error, forbidden
554        xml = u"""
555            <presence from='%s' type='error'>
556              <error type='auth'>
557                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
558              </error>
559            </presence>
560        """ % (self.roomJID)
561        self.stub.send(parseXml(xml))
562        return d
563
564
565    def test_joinBadJID(self):
566        """
567        Client joining a room and getting a jid-malformed error.
568        """
569
570        def cb(error):
571            self.assertEquals('jid-malformed', error.condition,
572                              'Wrong muc condition')
573
574        d = self.protocol.join(self.roomJID, self.nick)
575        self.assertFailure(d, StanzaError)
576        d.addCallback(cb)
577
578        # send back error, bad JID
579        xml = u"""
580            <presence from='%s' type='error'>
581              <error type='modify'>
582                <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
583              </error>
584            </presence>
585        """ % (self.occupantJID)
586        self.stub.send(parseXml(xml))
587        return d
588
589
590    def test_joinTimeout(self):
591        """
592        After not receiving a response to a join, errback with L{TimeoutError}.
593        """
594
595        d = self.protocol.join(self.roomJID, self.nick)
596        self.assertFailure(d, TimeoutError)
597        self.clock.advance(muc.DEFER_TIMEOUT)
598        return d
599
600
601    def test_joinPassword(self):
602        """
603        Sending a password via presence to a password protected room.
604        """
605
606        self.protocol.join(self.roomJID, self.nick, password='secret')
607
608        element = self.stub.output[-1]
609
610        self.assertTrue(xpath.matches(
611                u"/presence[@to='%s']/x/password"
612                    "[text()='secret']" % (self.occupantJID,),
613                element),
614            'Wrong presence stanza')
615
616
617    def test_nick(self):
618        """
619        Send a nick change to the server.
620        """
621        newNick = 'newNick'
622
623        def cb(presence):
624            self.assertEquals(JID(tuple=(self.roomIdentifier,
625                                         self.service,
626                                         newNick)),
627                              presence.sender)
628
629        d = self.protocol.nick(self.roomJID, newNick)
630        d.addCallback(cb)
631
632        element = self.stub.output[-1]
633        self.assertEquals('presence', element.name, "Need to be presence")
634        self.assertNotIdentical(None, element.x, 'No muc x element')
635
636        # send back user presence, nick changed
637        xml = u"""
638            <presence from='%s/%s'>
639              <x xmlns='http://jabber.org/protocol/muc#user'>
640                <item affiliation='member' role='participant'/>
641              </x>
642            </presence>
643        """ % (self.roomJID, newNick)
644        self.stub.send(parseXml(xml))
645        return d
646
647
648    def test_nickConflict(self):
649        """
650        If the server finds the new nick in conflict, the errback is called.
651        """
652        newNick = 'newNick'
653
654        d = self.protocol.nick(self.roomJID, newNick)
655        self.assertFailure(d, StanzaError)
656
657        element = self.stub.output[-1]
658        self.assertEquals('presence', element.name, "Need to be presence")
659        self.assertNotIdentical(None, element.x, 'No muc x element')
660
661        # send back error presence, nick conflicted
662        xml = u"""
663            <presence from='%s/%s' type='error'>
664                <x xmlns='http://jabber.org/protocol/muc'/>
665                <error type='cancel'>
666                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
667                </error>
668            </presence>
669        """ % (self.roomJID, newNick)
670        self.stub.send(parseXml(xml))
671        return d
672
673
674    def test_status(self):
675        """
676        Change status
677        """
678        def joined(_):
679            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
680            d.addCallback(statusChanged)
681            return d
682
683        def statusChanged(presence):
684            self.assertEqual(self.occupantJID, presence.sender)
685
686        # Join the room
687        d = self.protocol.join(self.roomJID, self.nick)
688        d.addCallback(joined)
689
690        # Receive presence back from the room: joined.
691        xml = u"""
692            <presence to='%s' from='%s'/>
693        """ % (self.userJID, self.occupantJID)
694        self.stub.send(parseXml(xml))
695
696        # The presence for the status change should have been sent now.
697        element = self.stub.output[-1]
698
699        self.assertEquals('presence', element.name, "Need to be presence")
700        self.assertTrue(getattr(element, 'x', None), 'No muc x element')
701
702        # send back user presence, status changed
703        xml = u"""
704            <presence from='%s'>
705              <x xmlns='http://jabber.org/protocol/muc#user'>
706                <item affiliation='member' role='participant'/>
707              </x>
708              <show>xa</show>
709              <status>testing MUC</status>
710            </presence>
711        """ % self.occupantJID
712        self.stub.send(parseXml(xml))
713
714        return d
715
716
717    def test_leave(self):
718        """
719        Client leaves a room
720        """
721        def joined(_):
722            return self.protocol.leave(self.roomJID)
723
724        # Join the room
725        d = self.protocol.join(self.roomJID, self.nick)
726        d.addCallback(joined)
727
728        # Receive presence back from the room: joined.
729        xml = u"""
730            <presence to='%s' from='%s'/>
731        """ % (self.userJID, self.occupantJID)
732        self.stub.send(parseXml(xml))
733
734        # The presence for leaving the room should have been sent now.
735        element = self.stub.output[-1]
736
737        self.assertEquals('unavailable', element['type'],
738                          'Unavailable is not being sent')
739
740        # Receive presence back from the room: left.
741        xml = u"""
742            <presence to='%s' from='%s' type='unavailable'/>
743        """ % (self.userJID, self.occupantJID)
744        self.stub.send(parseXml(xml))
745
746        return d
747
748
749    def test_groupChat(self):
750        """
751        Send private messages to muc entities.
752        """
753        self.protocol.groupChat(self.roomJID, u'This is a test')
754
755        message = self.stub.output[-1]
756
757        self.assertEquals('message', message.name)
758        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
759        self.assertEquals('groupchat', message.getAttribute('type'))
760        self.assertEquals(u'This is a test', unicode(message.body))
761
762
763    def test_chat(self):
764        """
765        Send private messages to muc entities.
766        """
767        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
768
769        self.protocol.chat(otherOccupantJID, u'This is a test')
770
771        message = self.stub.output[-1]
772
773        self.assertEquals('message', message.name)
774        self.assertEquals(otherOccupantJID.full(), message.getAttribute('to'))
775        self.assertEquals('chat', message.getAttribute('type'))
776        self.assertEquals(u'This is a test', unicode(message.body))
777
778
779    def test_subject(self):
780        """
781        Change subject of the room.
782        """
783        self.protocol.subject(self.roomJID, u'This is a test')
784
785        message = self.stub.output[-1]
786
787        self.assertEquals('message', message.name)
788        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
789        self.assertEquals('groupchat', message.getAttribute('type'))
790        self.assertEquals(u'This is a test', unicode(message.subject))
791
792
793    def test_invite(self):
794        """
795        Invite a user to a room
796        """
797        invitee = JID('other@example.org')
798
799        self.protocol.invite(self.roomJID, invitee, u'This is a test')
800
801        message = self.stub.output[-1]
802
803        self.assertEquals('message', message.name)
804        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
805        self.assertEquals(muc.NS_MUC_USER, message.x.uri)
806        self.assertEquals(muc.NS_MUC_USER, message.x.invite.uri)
807        self.assertEquals(invitee.full(), message.x.invite.getAttribute('to'))
808        self.assertEquals(muc.NS_MUC_USER, message.x.invite.reason.uri)
809        self.assertEquals(u'This is a test', unicode(message.x.invite.reason))
810
811
812    def test_getRegisterForm(self):
813        """
814        The response of a register form request should extract the form.
815        """
816
817        def cb(form):
818            self.assertEquals('form', form.formType)
819
820        d = self.protocol.getRegisterForm(self.roomJID)
821        d.addCallback(cb)
822
823        iq = self.stub.output[-1]
824
825        query = "/iq/query[@xmlns='%s']" % (muc.NS_REGISTER)
826        nodes = xpath.queryForNodes(query, iq)
827        self.assertNotIdentical(None, nodes, 'Missing query element')
828
829        self.assertRaises(StopIteration, next, nodes[0].elements())
830
831        xml = u"""
832            <iq from='%s' id='%s' to='%s' type='result'>
833              <query xmlns='jabber:iq:register'>
834                <x xmlns='jabber:x:data' type='form'>
835                  <field type='hidden'
836                         var='FORM_TYPE'>
837                    <value>http://jabber.org/protocol/muc#register</value>
838                  </field>
839                  <field label='Desired Nickname'
840                         type='text-single'
841                         var='muc#register_roomnick'>
842                    <required/>
843                  </field>
844                </x>
845              </query>
846            </iq>
847        """ % (self.roomJID, iq['id'], self.userJID)
848        self.stub.send(parseXml(xml))
849
850        return d
851
852
853    def test_register(self):
854        """
855        Client registering with a room.
856
857        http://xmpp.org/extensions/xep-0045.html#register
858        """
859
860        def cb(iq):
861            # check for a result
862            self.assertEquals('result', iq['type'], 'We did not get a result')
863
864        d = self.protocol.register(self.roomJID,
865                                   {'muc#register_roomnick': 'thirdwitch'})
866        d.addCallback(cb)
867
868        iq = self.stub.output[-1]
869
870        query = "/iq/query[@xmlns='%s']" % muc.NS_REGISTER
871        nodes = xpath.queryForNodes(query, iq)
872        self.assertNotIdentical(None, nodes, 'Invalid registration request')
873
874        form = data_form.findForm(nodes[0], muc.NS_MUC_REGISTER)
875        self.assertNotIdentical(None, form, 'Missing registration form')
876        self.assertEquals('submit', form.formType)
877        self.assertIn('muc#register_roomnick', form.fields)
878
879
880        response = toResponse(iq, 'result')
881        self.stub.send(response)
882        return d
883
884
885    def test_registerCancel(self):
886        """
887        Cancelling a registration request sends a cancel form.
888        """
889
890        d = self.protocol.register(self.roomJID, None)
891
892        iq = self.stub.output[-1]
893
894        query = "/iq/query[@xmlns='%s']" % muc.NS_REGISTER
895        nodes = xpath.queryForNodes(query, iq)
896        self.assertNotIdentical(None, nodes, 'Invalid registration request')
897
898        form = data_form.findForm(nodes[0], muc.NS_MUC_REGISTER)
899        self.assertNotIdentical(None, form, 'Missing registration form')
900        self.assertEquals('cancel', form.formType)
901
902        response = toResponse(iq, 'result')
903        self.stub.send(response)
904        return d
905
906
907    def test_voice(self):
908        """
909        Client requesting voice for a room.
910        """
911        self.protocol.voice(self.occupantJID)
912
913        m = self.stub.output[-1]
914
915        query = ("/message/x[@type='submit']/field/value"
916                    "[text()='%s']") % muc.NS_MUC_REQUEST
917        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
918
919
920    def test_history(self):
921        """
922        Converting a one to one chat to a multi-user chat.
923        """
924        archive = []
925        thread = "e0ffe42b28561960c6b12b944a092794b9683a38"
926        # create messages
927        element = domish.Element((None, 'message'))
928        element['to'] = 'testing@example.com'
929        element['type'] = 'chat'
930        element.addElement('body', None, 'test')
931        element.addElement('thread', None, thread)
932
933        archive.append({'stanza': element,
934                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
935                                              tzinfo=tzutc())})
936
937        element = domish.Element((None, 'message'))
938        element['to'] = 'testing2@example.com'
939        element['type'] = 'chat'
940        element.addElement('body', None, 'yo')
941        element.addElement('thread', None, thread)
942
943        archive.append({'stanza': element,
944                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
945                                              tzinfo=tzutc())})
946
947        self.protocol.history(self.occupantJID, archive)
948
949
950        while len(self.stub.output)>0:
951            element = self.stub.output.pop()
952            # check for delay element
953            self.assertEquals('message', element.name, 'Wrong stanza')
954            self.assertTrue(xpath.matches("/message/delay", element),
955                            'Invalid history stanza')
956
957
958    def test_getConfiguration(self):
959        """
960        The response of a configure form request should extract the form.
961        """
962
963        def cb(form):
964            self.assertEquals('form', form.formType)
965
966        d = self.protocol.getConfiguration(self.roomJID)
967        d.addCallback(cb)
968
969        iq = self.stub.output[-1]
970
971        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
972        nodes = xpath.queryForNodes(query, iq)
973        self.assertNotIdentical(None, nodes, 'Missing query element')
974
975        self.assertRaises(StopIteration, next, nodes[0].elements())
976
977        xml = u"""
978            <iq from='%s' id='%s' to='%s' type='result'>
979              <query xmlns='http://jabber.org/protocol/muc#owner'>
980                <x xmlns='jabber:x:data' type='form'>
981                  <field type='hidden'
982                         var='FORM_TYPE'>
983                    <value>http://jabber.org/protocol/muc#roomconfig</value>
984                  </field>
985                  <field label='Natural-Language Room Name'
986                         type='text-single'
987                         var='muc#roomconfig_roomname'/>
988                </x>
989              </query>
990            </iq>
991        """ % (self.roomJID, iq['id'], self.userJID)
992        self.stub.send(parseXml(xml))
993
994        return d
995
996
997    def test_getConfigurationNoOptions(self):
998        """
999        The response of a configure form request should extract the form.
1000        """
1001
1002        def cb(form):
1003            self.assertIdentical(None, form)
1004
1005        d = self.protocol.getConfiguration(self.roomJID)
1006        d.addCallback(cb)
1007
1008        iq = self.stub.output[-1]
1009
1010        xml = u"""
1011            <iq from='%s' id='%s' to='%s' type='result'>
1012              <query xmlns='http://jabber.org/protocol/muc#owner'/>
1013            </iq>
1014        """ % (self.roomJID, iq['id'], self.userJID)
1015        self.stub.send(parseXml(xml))
1016
1017        return d
1018
1019
1020    def test_configure(self):
1021        """
1022        Default configure and changing the room name.
1023        """
1024
1025        def cb(iq):
1026            self.assertEquals('result', iq['type'], 'Not a result')
1027
1028        values = {'muc#roomconfig_roomname': self.roomIdentifier}
1029
1030        d = self.protocol.configure(self.roomJID, values)
1031        d.addCallback(cb)
1032
1033        iq = self.stub.output[-1]
1034
1035        self.assertEquals('set', iq.getAttribute('type'))
1036        self.assertEquals(self.roomJID.full(), iq.getAttribute('to'))
1037
1038        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
1039        nodes = xpath.queryForNodes(query, iq)
1040        self.assertNotIdentical(None, nodes, 'Bad configure request')
1041
1042        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
1043        self.assertNotIdentical(None, form, 'Missing configuration form')
1044        self.assertEquals('submit', form.formType)
1045
1046        response = toResponse(iq, 'result')
1047        self.stub.send(response)
1048        return d
1049
1050
1051    def test_configureEmpty(self):
1052        """
1053        Accept default configuration by sending an empty form.
1054        """
1055
1056        values = {}
1057
1058        d = self.protocol.configure(self.roomJID, values)
1059
1060        iq = self.stub.output[-1]
1061
1062        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
1063        nodes = xpath.queryForNodes(query, iq)
1064
1065        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
1066        self.assertNotIdentical(None, form, 'Missing configuration form')
1067        self.assertEquals('submit', form.formType)
1068
1069        response = toResponse(iq, 'result')
1070        self.stub.send(response)
1071        return d
1072
1073
1074    def test_configureCancel(self):
1075        """
1076        Cancelling room configuration should send a cancel form.
1077        """
1078
1079        d = self.protocol.configure(self.roomJID, None)
1080
1081        iq = self.stub.output[-1]
1082
1083        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
1084        nodes = xpath.queryForNodes(query, iq)
1085
1086        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
1087        self.assertNotIdentical(None, form, 'Missing configuration form')
1088        self.assertEquals('cancel', form.formType)
1089
1090        response = toResponse(iq, 'result')
1091        self.stub.send(response)
1092        return d
1093
1094
1095    def test_getMemberList(self):
1096        """
1097        Retrieving the member list returns a list of L{muc.AdminItem}s
1098
1099        The request asks for the affiliation C{'member'}.
1100        """
1101        def cb(items):
1102            self.assertEquals(1, len(items))
1103            item = items[0]
1104            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1105            self.assertEquals(u'thirdwitch', item.nick)
1106            self.assertEquals(u'member', item.affiliation)
1107
1108        d = self.protocol.getMemberList(self.roomJID)
1109        d.addCallback(cb)
1110
1111        iq = self.stub.output[-1]
1112        self.assertEquals('get', iq.getAttribute('type'))
1113        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1114                                                              muc.NS_MUC_ADMIN)
1115        items = xpath.queryForNodes(query, iq)
1116        self.assertNotIdentical(None, items)
1117        self.assertEquals(1, len(items))
1118        self.assertEquals('member', items[0].getAttribute('affiliation'))
1119
1120        response = toResponse(iq, 'result')
1121        query = response.addElement((NS_MUC_ADMIN, 'query'))
1122        item = query.addElement('item')
1123        item['affiliation'] ='member'
1124        item['jid'] = 'hag66@shakespeare.lit'
1125        item['nick'] = 'thirdwitch'
1126        item['role'] = 'participant'
1127        self.stub.send(response)
1128
1129        return d
1130
1131
1132    def test_getAdminList(self):
1133        """
1134        Retrieving the admin list returns a list of L{muc.AdminItem}s
1135
1136        The request asks for the affiliation C{'admin'}.
1137        """
1138        d = self.protocol.getAdminList(self.roomJID)
1139
1140        iq = self.stub.output[-1]
1141        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1142                                                              muc.NS_MUC_ADMIN)
1143        items = xpath.queryForNodes(query, iq)
1144        self.assertEquals('admin', items[0].getAttribute('affiliation'))
1145
1146        response = toResponse(iq, 'result')
1147        query = response.addElement((NS_MUC_ADMIN, 'query'))
1148        self.stub.send(response)
1149
1150        return d
1151
1152
1153    def test_getBanList(self):
1154        """
1155        Retrieving the ban list returns a list of L{muc.AdminItem}s
1156
1157        The request asks for the affiliation C{'outcast'}.
1158        """
1159        def cb(items):
1160            self.assertEquals(1, len(items))
1161            item = items[0]
1162            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1163            self.assertEquals(u'outcast', item.affiliation)
1164            self.assertEquals(u'Trouble making', item.reason)
1165
1166        d = self.protocol.getBanList(self.roomJID)
1167        d.addCallback(cb)
1168
1169        iq = self.stub.output[-1]
1170        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1171                                                              muc.NS_MUC_ADMIN)
1172        items = xpath.queryForNodes(query, iq)
1173        self.assertEquals('outcast', items[0].getAttribute('affiliation'))
1174
1175        response = toResponse(iq, 'result')
1176        query = response.addElement((NS_MUC_ADMIN, 'query'))
1177        item = query.addElement('item')
1178        item['affiliation'] ='outcast'
1179        item['jid'] = 'hag66@shakespeare.lit'
1180        item.addElement('reason', content='Trouble making')
1181        self.stub.send(response)
1182
1183        return d
1184
1185
1186    def test_getOwnerList(self):
1187        """
1188        Retrieving the owner list returns a list of L{muc.AdminItem}s
1189
1190        The request asks for the affiliation C{'owner'}.
1191        """
1192        d = self.protocol.getOwnerList(self.roomJID)
1193
1194        iq = self.stub.output[-1]
1195        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1196                                                              muc.NS_MUC_ADMIN)
1197        items = xpath.queryForNodes(query, iq)
1198        self.assertEquals('owner', items[0].getAttribute('affiliation'))
1199
1200        response = toResponse(iq, 'result')
1201        query = response.addElement((NS_MUC_ADMIN, 'query'))
1202        self.stub.send(response)
1203
1204        return d
1205
1206
1207    def test_getModeratorList(self):
1208        """
1209        Retrieving the moderator returns a list of L{muc.AdminItem}s.
1210
1211        The request asks for the role C{'moderator'}.
1212        """
1213
1214        def cb(items):
1215            self.assertEquals(1, len(items))
1216            item = items[0]
1217            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1218            self.assertEquals(u'thirdwitch', item.nick)
1219            self.assertEquals(u'moderator', item.role)
1220
1221        d = self.protocol.getModeratorList(self.roomJID)
1222        d.addCallback(cb)
1223
1224        iq = self.stub.output[-1]
1225        self.assertEquals('get', iq.getAttribute('type'))
1226        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1227                                                              muc.NS_MUC_ADMIN)
1228        items = xpath.queryForNodes(query, iq)
1229        self.assertNotIdentical(None, items)
1230        self.assertEquals(1, len(items))
1231        self.assertEquals('moderator', items[0].getAttribute('role'))
1232
1233        response = toResponse(iq, 'result')
1234        query = response.addElement((NS_MUC_ADMIN, 'query'))
1235        item = query.addElement('item')
1236        item['affiliation'] ='member'
1237        item['jid'] = 'hag66@shakespeare.lit'
1238        item['nick'] = 'thirdwitch'
1239        item['role'] = 'moderator'
1240        self.stub.send(response)
1241
1242        return d
1243
1244
1245    def test_modifyAffiliationList(self):
1246
1247        entities = [JID('user1@test.example.org'),
1248                    JID('user2@test.example.org')]
1249        d = self.protocol.modifyAffiliationList(self.roomJID, entities,
1250                                                'admin')
1251
1252        iq = self.stub.output[-1]
1253        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1254                                                              muc.NS_MUC_ADMIN)
1255        items = xpath.queryForNodes(query, iq)
1256        self.assertNotIdentical(None, items)
1257        self.assertEquals(entities[0], JID(items[0].getAttribute('jid')))
1258        self.assertEquals('admin', items[0].getAttribute('affiliation'))
1259        self.assertEquals(entities[1], JID(items[1].getAttribute('jid')))
1260        self.assertEquals('admin', items[1].getAttribute('affiliation'))
1261
1262        # Send a response to have the deferred fire.
1263        response = toResponse(iq, 'result')
1264        self.stub.send(response)
1265        return d
1266
1267
1268    def test_grantVoice(self):
1269        """
1270        Granting voice sends request to set role to 'participant'.
1271        """
1272        nick = 'TroubleMaker'
1273        def cb(give_voice):
1274            self.assertTrue(give_voice, 'Did not give voice user')
1275
1276        d = self.protocol.grantVoice(self.roomJID, nick,
1277                                     sender=self.userJID)
1278        d.addCallback(cb)
1279
1280        iq = self.stub.output[-1]
1281
1282        query = (u"/iq[@type='set' and @to='%s']/query/item"
1283                     "[@role='participant']") % self.roomJID
1284        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1285
1286        response = toResponse(iq, 'result')
1287        self.stub.send(response)
1288        return d
1289
1290
1291    def test_revokeVoice(self):
1292        """
1293        Revoking voice sends request to set role to 'visitor'.
1294        """
1295        nick = 'TroubleMaker'
1296
1297        d = self.protocol.revokeVoice(self.roomJID, nick,
1298                                      reason="Trouble maker",
1299                                      sender=self.userJID)
1300
1301        iq = self.stub.output[-1]
1302
1303        query = (u"/iq[@type='set' and @to='%s']/query/item"
1304                     "[@role='visitor']") % self.roomJID
1305        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1306
1307        response = toResponse(iq, 'result')
1308        self.stub.send(response)
1309        return d
1310
1311
1312    def test_grantModerator(self):
1313        """
1314        Granting moderator privileges sends request to set role to 'moderator'.
1315        """
1316        nick = 'TroubleMaker'
1317
1318        d = self.protocol.grantModerator(self.roomJID, nick,
1319                                         sender=self.userJID)
1320
1321        iq = self.stub.output[-1]
1322
1323        query = (u"/iq[@type='set' and @to='%s']/query/item"
1324                     "[@role='moderator']") % self.roomJID
1325        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1326
1327        response = toResponse(iq, 'result')
1328        self.stub.send(response)
1329        return d
1330
1331
1332    def test_ban(self):
1333        """
1334        Ban an entity in a room.
1335        """
1336        banned = JID('ban@jabber.org/TroubleMaker')
1337
1338        def cb(banned):
1339            self.assertTrue(banned, 'Did not ban user')
1340
1341        d = self.protocol.ban(self.roomJID, banned, reason='Spam',
1342                              sender=self.userJID)
1343        d.addCallback(cb)
1344
1345        iq = self.stub.output[-1]
1346
1347        self.assertTrue(xpath.matches(
1348                u"/iq[@type='set' and @to='%s']/query/item"
1349                    "[@affiliation='outcast']" % (self.roomJID,),
1350                iq),
1351            'Wrong ban stanza')
1352
1353        response = toResponse(iq, 'result')
1354        self.stub.send(response)
1355
1356        return d
1357
1358
1359    def test_kick(self):
1360        """
1361        Kick an entity from a room.
1362        """
1363        nick = 'TroubleMaker'
1364
1365        def cb(kicked):
1366            self.assertTrue(kicked, 'Did not kick user')
1367
1368        d = self.protocol.kick(self.roomJID, nick, reason='Spam',
1369                               sender=self.userJID)
1370        d.addCallback(cb)
1371
1372        iq = self.stub.output[-1]
1373
1374        self.assertTrue(xpath.matches(
1375                u"/iq[@type='set' and @to='%s']/query/item"
1376                    "[@role='none']" % (self.roomJID,),
1377                iq),
1378            'Wrong kick stanza')
1379
1380        response = toResponse(iq, 'result')
1381        self.stub.send(response)
1382
1383        return d
1384
1385
1386    def test_destroy(self):
1387        """
1388        Destroy a room.
1389        """
1390        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1391                                  alternate=JID('other@%s' % self.service),
1392                                  password='secret')
1393
1394        iq = self.stub.output[-1]
1395
1396        query = ("/iq[@type='set']/query[@xmlns='%s']/destroy[@xmlns='%s']" %
1397                 (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER))
1398
1399        nodes = xpath.queryForNodes(query, iq)
1400        self.assertNotIdentical(None, nodes, 'Bad configure request')
1401        destroy = nodes[0]
1402        self.assertEquals('Time to leave', unicode(destroy.reason))
1403
1404        response = toResponse(iq, 'result')
1405        self.stub.send(response)
1406        return d
1407
1408
1409
1410class MUCClientTest(unittest.TestCase):
1411    """
1412    Tests for C{muc.MUCClient}.
1413    """
1414
1415    def setUp(self):
1416        self.clock = task.Clock()
1417        self.sessionManager = TestableStreamManager(reactor=self.clock)
1418        self.stub = self.sessionManager.stub
1419        self.protocol = muc.MUCClient(reactor=self.clock)
1420        self.protocol.setHandlerParent(self.sessionManager)
1421
1422        self.roomIdentifier = 'test'
1423        self.service  = 'conference.example.org'
1424        self.nick = 'Nick'
1425
1426        self.occupantJID = JID(tuple=(self.roomIdentifier,
1427                                      self.service,
1428                                      self.nick))
1429        self.roomJID = self.occupantJID.userhostJID()
1430        self.userJID = JID('test@example.org/Testing')
1431
1432
1433    def _createRoom(self):
1434        """
1435        A helper method to create a test room.
1436        """
1437        # create a room
1438        room = muc.Room(self.roomJID, self.nick)
1439        self.protocol._addRoom(room)
1440        return room
1441
1442
1443    def test_interface(self):
1444        """
1445        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
1446        """
1447        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
1448
1449
1450    def _testPresence(self, sender='', available=True):
1451        """
1452        Helper for presence tests.
1453        """
1454        def userUpdatedStatus(room, user, show, status):
1455            self.fail("Unexpected call to userUpdatedStatus")
1456
1457        def userJoinedRoom(room, user):
1458            self.fail("Unexpected call to userJoinedRoom")
1459
1460        if available:
1461            available = ""
1462        else:
1463            available = " type='unavailable'"
1464
1465        if sender:
1466            sender = u" from='%s'" % sender
1467
1468        xml = u"""
1469            <presence to='%s'%s%s>
1470              <x xmlns='http://jabber.org/protocol/muc#user'>
1471                <item affiliation='member' role='participant'/>
1472              </x>
1473            </presence>
1474        """ % (self.userJID, sender, available)
1475
1476        self.protocol.userUpdatedStatus = userUpdatedStatus
1477        self.protocol.userJoinedRoom = userJoinedRoom
1478        self.stub.send(parseXml(xml))
1479
1480
1481    def test_availableReceivedEmptySender(self):
1482        """
1483        Availability presence from empty sender is ignored.
1484        """
1485        self._testPresence(sender='')
1486
1487
1488    def test_availableReceivedNotInRoom(self):
1489        """
1490        Availability presence from unknown entities is ignored.
1491        """
1492        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1493        self._testPresence(sender=otherOccupantJID)
1494
1495
1496    def test_unavailableReceivedEmptySender(self):
1497        """
1498        Availability presence from empty sender is ignored.
1499        """
1500        self._testPresence(sender='', available=False)
1501
1502
1503    def test_unavailableReceivedNotInRoom(self):
1504        """
1505        Availability presence from unknown entities is ignored.
1506        """
1507        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1508        self._testPresence(sender=otherOccupantJID, available=False)
1509
1510
1511    def test_unavailableReceivedNotInRoster(self):
1512        """
1513        Availability presence from unknown entities is ignored.
1514        """
1515        room = self._createRoom()
1516        user = muc.User(self.nick)
1517        room.addUser(user)
1518        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1519        self._testPresence(sender=otherOccupantJID, available=False)
1520
1521
1522    def test_userJoinedRoom(self):
1523        """
1524        Joins by others to a room we're in are passed to userJoinedRoom
1525        """
1526        xml = """
1527            <presence to='%s' from='%s'>
1528              <x xmlns='http://jabber.org/protocol/muc#user'>
1529                <item affiliation='member' role='participant'/>
1530              </x>
1531            </presence>
1532        """ % (self.userJID.full(), self.occupantJID.full())
1533
1534        # create a room
1535        self._createRoom()
1536
1537        def userJoinedRoom(room, user):
1538            self.assertEquals(self.roomJID, room.roomJID,
1539                              'Wrong room name')
1540            self.assertTrue(room.inRoster(user), 'User not in roster')
1541
1542        d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom)
1543        self.stub.send(parseXml(xml))
1544        return d
1545
1546
1547    def test_receivedSubject(self):
1548        """
1549        Subject received from a room we're in are passed to receivedSubject.
1550        """
1551        xml = u"""
1552            <message to='%s' from='%s' type='groupchat'>
1553              <subject>test</subject>
1554            </message>
1555        """ % (self.userJID, self.occupantJID)
1556
1557        self._createRoom()
1558
1559        # add user to room
1560        user = muc.User(self.nick)
1561        room = self.protocol._getRoom(self.roomJID)
1562        room.addUser(user)
1563
1564        def receivedSubject(room, user, subject):
1565            self.assertEquals('test', subject, "Wrong group chat message")
1566            self.assertEquals(self.roomJID, room.roomJID,
1567                              'Wrong room name')
1568            self.assertEquals(self.nick, user.nick)
1569
1570        d, self.protocol.receivedSubject = calledAsync(receivedSubject)
1571        self.stub.send(parseXml(xml))
1572        return d
1573
1574
1575    def test_receivedSubjectNotOverridden(self):
1576        """
1577        Not overriding receivedSubject is ok.
1578        """
1579        xml = u"""
1580            <message to='%s' from='%s' type='groupchat'>
1581              <subject>test</subject>
1582            </message>
1583        """ % (self.userJID, self.occupantJID)
1584
1585        self._createRoom()
1586        self.stub.send(parseXml(xml))
1587
1588
1589    def test_receivedGroupChat(self):
1590        """
1591        Messages received from a room we're in are passed to receivedGroupChat.
1592        """
1593        xml = u"""
1594            <message to='test@test.com' from='%s' type='groupchat'>
1595              <body>test</body>
1596            </message>
1597        """ % (self.occupantJID)
1598
1599        self._createRoom()
1600
1601        def receivedGroupChat(room, user, message):
1602            self.assertEquals('test', message.body, "Wrong group chat message")
1603            self.assertEquals(self.roomJID, room.roomJID,
1604                              'Wrong room name')
1605
1606        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1607        self.stub.send(parseXml(xml))
1608        return d
1609
1610
1611    def test_receivedGroupChatRoom(self):
1612        """
1613        Messages received from the room itself have C{user} set to C{None}.
1614        """
1615        xml = u"""
1616            <message to='test@test.com' from='%s' type='groupchat'>
1617              <body>test</body>
1618            </message>
1619        """ % (self.roomJID)
1620
1621        self._createRoom()
1622
1623        def receivedGroupChat(room, user, message):
1624            self.assertIdentical(None, user)
1625
1626        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1627        self.stub.send(parseXml(xml))
1628        return d
1629
1630
1631    def test_receivedGroupChatNotInRoom(self):
1632        """
1633        Messages received from a room we're not in are ignored.
1634        """
1635        xml = u"""
1636            <message to='test@test.com' from='%s' type='groupchat'>
1637              <body>test</body>
1638            </message>
1639        """ % (self.occupantJID)
1640
1641        def receivedGroupChat(room, user, message):
1642            self.fail("Unexpected call to receivedGroupChat")
1643
1644        self.protocol.receivedGroupChat = receivedGroupChat
1645        self.stub.send(parseXml(xml))
1646
1647
1648    def test_receivedGroupChatNotOverridden(self):
1649        """
1650        Not overriding receivedGroupChat is ok.
1651        """
1652        xml = u"""
1653            <message to='test@test.com' from='%s' type='groupchat'>
1654              <body>test</body>
1655            </message>
1656        """ % (self.occupantJID)
1657
1658        self._createRoom()
1659        self.stub.send(parseXml(xml))
1660
1661
1662    def test_join(self):
1663        """
1664        Joining a room waits for confirmation, deferred fires room.
1665        """
1666
1667        def cb(room):
1668            self.assertEqual(self.roomJID, room.roomJID)
1669            self.assertFalse(room.locked)
1670
1671        d = self.protocol.join(self.roomJID, self.nick)
1672        d.addCallback(cb)
1673
1674        # send back user presence, they joined
1675        xml = """
1676            <presence from='%s@%s/%s'>
1677              <x xmlns='http://jabber.org/protocol/muc#user'>
1678                <item affiliation='member' role='participant'/>
1679              </x>
1680            </presence>
1681        """ % (self.roomIdentifier, self.service, self.nick)
1682        self.stub.send(parseXml(xml))
1683        return d
1684
1685
1686    def test_joinLocked(self):
1687        """
1688        A new room is locked by default.
1689        """
1690
1691        def cb(room):
1692            self.assertTrue(room.locked, "Room is not marked as locked")
1693
1694        d = self.protocol.join(self.roomJID, self.nick)
1695        d.addCallback(cb)
1696
1697        # send back user presence, they joined
1698        xml = """
1699            <presence from='%s@%s/%s'>
1700              <x xmlns='http://jabber.org/protocol/muc#user'>
1701                <item affiliation='owner' role='moderator'/>
1702                <status code="110"/>
1703                <status code="201"/>
1704              </x>
1705            </presence>
1706        """ % (self.roomIdentifier, self.service, self.nick)
1707        self.stub.send(parseXml(xml))
1708        return d
1709
1710
1711    def test_joinForbidden(self):
1712        """
1713        A forbidden error in response to a join errbacks with L{StanzaError}.
1714        """
1715
1716        def cb(error):
1717            self.assertEquals('forbidden', error.condition,
1718                              'Wrong muc condition')
1719            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1720
1721
1722        d = self.protocol.join(self.roomJID, self.nick)
1723        self.assertFailure(d, StanzaError)
1724        d.addCallback(cb)
1725
1726        # send back error, forbidden
1727        xml = u"""
1728            <presence from='%s' type='error'>
1729              <error type='auth'>
1730                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1731              </error>
1732            </presence>
1733        """ % (self.occupantJID)
1734        self.stub.send(parseXml(xml))
1735        return d
1736
1737
1738    def test_userLeftRoom(self):
1739        """
1740        Unavailable presence from a participant removes it from the room.
1741        """
1742
1743        xml = u"""
1744            <presence to='%s' from='%s' type='unavailable'/>
1745        """ % (self.userJID, self.occupantJID)
1746
1747        # create a room
1748        self._createRoom()
1749
1750        # add user to room
1751        user = muc.User(self.nick)
1752        room = self.protocol._getRoom(self.roomJID)
1753        room.addUser(user)
1754
1755        def userLeftRoom(room, user):
1756            self.assertEquals(self.roomJID, room.roomJID,
1757                              'Wrong room name')
1758            self.assertFalse(room.inRoster(user), 'User in roster')
1759
1760        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
1761        self.stub.send(parseXml(xml))
1762        return d
1763
1764
1765    def test_receivedHistory(self):
1766        """
1767        Receiving history on room join.
1768        """
1769        xml = u"""
1770            <message to='test@test.com' from='%s' type='groupchat'>
1771              <body>test</body>
1772              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1773                                            from="%s"/>
1774            </message>
1775        """ % (self.occupantJID, self.userJID)
1776
1777        self._createRoom()
1778
1779
1780        def receivedHistory(room, user, message):
1781            self.assertEquals('test', message.body, "wrong message body")
1782            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
1783            self.assertEquals(stamp, message.delay.stamp,
1784                             'Does not have a history stamp')
1785
1786        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
1787        self.stub.send(parseXml(xml))
1788        return d
1789
1790
1791    def test_receivedHistoryNotOverridden(self):
1792        """
1793        Not overriding receivedHistory is ok.
1794        """
1795        xml = u"""
1796            <message to='test@test.com' from='%s' type='groupchat'>
1797              <body>test</body>
1798              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1799                                            from="%s"/>
1800            </message>
1801        """ % (self.occupantJID, self.userJID)
1802
1803        self._createRoom()
1804        self.stub.send(parseXml(xml))
1805
1806
1807    def test_nickConflict(self):
1808        """
1809        If the server finds the new nick in conflict, the errback is called.
1810        """
1811
1812        def cb(failure, room):
1813            user = room.getUser(otherNick)
1814            self.assertNotIdentical(None, user)
1815            self.assertEqual(otherJID, user.entity)
1816
1817        def joined(room):
1818            d = self.protocol.nick(room.roomJID, otherNick)
1819            self.assertFailure(d, StanzaError)
1820            d.addCallback(cb, room)
1821
1822        otherJID = JID('other@example.org/Home')
1823        otherNick = 'otherNick'
1824
1825        d = self.protocol.join(self.roomJID, self.nick)
1826        d.addCallback(joined)
1827
1828        # Send back other partipant's presence.
1829        xml = u"""
1830            <presence from='%s/%s'>
1831              <x xmlns='http://jabber.org/protocol/muc#user'>
1832                <item affiliation='member' role='participant' jid='%s'/>
1833              </x>
1834            </presence>
1835        """ % (self.roomJID, otherNick, otherJID)
1836        self.stub.send(parseXml(xml))
1837
1838        # send back user presence, they joined
1839        xml = u"""
1840            <presence from='%s/%s'>
1841              <x xmlns='http://jabber.org/protocol/muc#user'>
1842                <item affiliation='member' role='participant'/>
1843              </x>
1844            </presence>
1845        """ % (self.roomJID, self.nick)
1846        self.stub.send(parseXml(xml))
1847
1848        # send back error presence, nick conflicted
1849        xml = u"""
1850            <presence from='%s/%s' type='error'>
1851                <x xmlns='http://jabber.org/protocol/muc'/>
1852                <error type='cancel'>
1853                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1854                </error>
1855            </presence>
1856        """ % (self.roomJID, otherNick)
1857        self.stub.send(parseXml(xml))
1858        return d
1859
1860
1861    def test_nick(self):
1862        """
1863        Send a nick change to the server.
1864        """
1865        newNick = 'newNick'
1866
1867        room = self._createRoom()
1868
1869        def joined(room):
1870            self.assertEqual(self.roomJID, room.roomJID)
1871            self.assertEqual(newNick, room.nick)
1872            user = room.getUser(newNick)
1873            self.assertNotIdentical(None, user)
1874            self.assertEqual(newNick, user.nick)
1875
1876        d = self.protocol.nick(self.roomJID, newNick)
1877        d.addCallback(joined)
1878
1879        # Nick should not have been changed, yet, as we haven't gotten
1880        # confirmation, yet.
1881
1882        self.assertEquals(self.nick, room.nick)
1883
1884        # send back user presence, nick changed
1885        xml = u"""
1886            <presence from='%s/%s'>
1887              <x xmlns='http://jabber.org/protocol/muc#user'>
1888                <item affiliation='member' role='participant'/>
1889              </x>
1890            </presence>
1891        """ % (self.roomJID, newNick)
1892
1893        self.stub.send(parseXml(xml))
1894        return d
1895
1896
1897    def test_leave(self):
1898        """
1899        Client leaves a room
1900        """
1901        def joined(_):
1902            return self.protocol.leave(self.roomJID)
1903
1904        def left(_):
1905            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1906
1907        # Join the room
1908        d = self.protocol.join(self.roomJID, self.nick)
1909        d.addCallback(joined)
1910        d.addCallback(left)
1911
1912        # Receive presence back from the room: joined.
1913        xml = u"""
1914            <presence to='%s' from='%s'/>
1915        """ % (self.userJID, self.occupantJID)
1916        self.stub.send(parseXml(xml))
1917
1918        # Receive presence back from the room: left.
1919        xml = u"""
1920            <presence to='%s' from='%s' type='unavailable'/>
1921        """ % (self.userJID, self.occupantJID)
1922        self.stub.send(parseXml(xml))
1923
1924        return d
1925
1926
1927    def test_status(self):
1928        """
1929        Change status
1930        """
1931        def joined(_):
1932            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
1933            d.addCallback(statusChanged)
1934            return d
1935
1936        def statusChanged(room):
1937            self.assertEqual(self.roomJID, room.roomJID)
1938            user = room.getUser(self.nick)
1939            self.assertNotIdentical(None, user, 'User not found')
1940            self.assertEqual('testing MUC', user.status, 'Wrong status')
1941            self.assertEqual('xa', user.show, 'Wrong show')
1942
1943        # Join the room
1944        d = self.protocol.join(self.roomJID, self.nick)
1945        d.addCallback(joined)
1946
1947        # Receive presence back from the room: joined.
1948        xml = u"""
1949            <presence to='%s' from='%s'/>
1950        """ % (self.userJID, self.occupantJID)
1951        self.stub.send(parseXml(xml))
1952
1953        # send back user presence, status changed
1954        xml = u"""
1955            <presence from='%s'>
1956              <x xmlns='http://jabber.org/protocol/muc#user'>
1957                <item affiliation='member' role='participant'/>
1958              </x>
1959              <show>xa</show>
1960              <status>testing MUC</status>
1961            </presence>
1962        """ % self.occupantJID
1963
1964        self.stub.send(parseXml(xml))
1965        return d
1966
1967
1968    def test_destroy(self):
1969        """
1970        Destroy a room.
1971        """
1972        def destroyed(_):
1973            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1974
1975        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1976                                  alternate=JID('other@%s' % self.service),
1977                                  password='secret')
1978        d.addCallback(destroyed)
1979
1980        iq = self.stub.output[-1]
1981        response = toResponse(iq, 'result')
1982        self.stub.send(response)
1983        return d
Note: See TracBrowser for help on using the repository browser.