source: wokkel/test/test_muc.py @ 185:a7e10201fb3c

Last change on this file since 185:a7e10201fb3c was 185:a7e10201fb3c, checked in by Ralph Meijer <ralphm@…>, 7 years ago

Do proper fallback for Request.stanzaType.

If stanzaType is not passed to the constructor of
wokkel.generic.Request, it should default to the value in the class
variable, not get.

Author: ralphm
Fixes: #80

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