source: wokkel/test/test_muc.py @ 160:33ef849a77d8

Last change on this file since 160:33ef849a77d8 was 160:33ef849a77d8, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Use symbolic constants instead of integers MUC status code.

Instead of using normal constant values for representing MUC status codes,
UserPresence now uses twisted.python.constants to define these. This
makes code better to understand and helps debugging.

If a user presence includes one or more status codes, they are stored in the
mucStatuses attribute, as an instance of Statuses. This replaces the
former statusCodes attribute.

File size: 59.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_voice(self):
883        """
884        Client requesting voice for a room.
885        """
886        self.protocol.voice(self.occupantJID)
887
888        m = self.stub.output[-1]
889
890        query = ("/message/x[@type='submit']/field/value"
891                    "[text()='%s']") % muc.NS_MUC_REQUEST
892        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
893
894
895    def test_history(self):
896        """
897        Converting a one to one chat to a multi-user chat.
898        """
899        archive = []
900        thread = "e0ffe42b28561960c6b12b944a092794b9683a38"
901        # create messages
902        element = domish.Element((None, 'message'))
903        element['to'] = 'testing@example.com'
904        element['type'] = 'chat'
905        element.addElement('body', None, 'test')
906        element.addElement('thread', None, thread)
907
908        archive.append({'stanza': element,
909                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
910                                              tzinfo=tzutc())})
911
912        element = domish.Element((None, 'message'))
913        element['to'] = 'testing2@example.com'
914        element['type'] = 'chat'
915        element.addElement('body', None, 'yo')
916        element.addElement('thread', None, thread)
917
918        archive.append({'stanza': element,
919                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
920                                              tzinfo=tzutc())})
921
922        self.protocol.history(self.occupantJID, archive)
923
924
925        while len(self.stub.output)>0:
926            element = self.stub.output.pop()
927            # check for delay element
928            self.assertEquals('message', element.name, 'Wrong stanza')
929            self.assertTrue(xpath.matches("/message/delay", element),
930                            'Invalid history stanza')
931
932
933    def test_getConfiguration(self):
934        """
935        The response of a configure form request should extract the form.
936        """
937
938        def cb(form):
939            self.assertEquals('form', form.formType)
940
941        d = self.protocol.getConfiguration(self.roomJID)
942        d.addCallback(cb)
943
944        iq = self.stub.output[-1]
945
946        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
947        nodes = xpath.queryForNodes(query, iq)
948        self.assertNotIdentical(None, nodes, 'Missing query element')
949
950        self.assertRaises(StopIteration, nodes[0].elements().next)
951
952        xml = u"""
953            <iq from='%s' id='%s' to='%s' type='result'>
954              <query xmlns='http://jabber.org/protocol/muc#owner'>
955                <x xmlns='jabber:x:data' type='form'>
956                  <field type='hidden'
957                         var='FORM_TYPE'>
958                    <value>http://jabber.org/protocol/muc#roomconfig</value>
959                  </field>
960                  <field label='Natural-Language Room Name'
961                         type='text-single'
962                         var='muc#roomconfig_roomname'/>
963                </x>
964              </query>
965            </iq>
966        """ % (self.roomJID, iq['id'], self.userJID)
967        self.stub.send(parseXml(xml))
968
969        return d
970
971
972    def test_getConfigurationNoOptions(self):
973        """
974        The response of a configure form request should extract the form.
975        """
976
977        def cb(form):
978            self.assertIdentical(None, form)
979
980        d = self.protocol.getConfiguration(self.roomJID)
981        d.addCallback(cb)
982
983        iq = self.stub.output[-1]
984
985        xml = u"""
986            <iq from='%s' id='%s' to='%s' type='result'>
987              <query xmlns='http://jabber.org/protocol/muc#owner'/>
988            </iq>
989        """ % (self.roomJID, iq['id'], self.userJID)
990        self.stub.send(parseXml(xml))
991
992        return d
993
994
995    def test_configure(self):
996        """
997        Default configure and changing the room name.
998        """
999
1000        def cb(iq):
1001            self.assertEquals('result', iq['type'], 'Not a result')
1002
1003        values = {'muc#roomconfig_roomname': self.roomIdentifier}
1004
1005        d = self.protocol.configure(self.roomJID, values)
1006        d.addCallback(cb)
1007
1008        iq = self.stub.output[-1]
1009
1010        self.assertEquals('set', iq.getAttribute('type'))
1011        self.assertEquals(self.roomJID.full(), iq.getAttribute('to'))
1012
1013        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
1014        nodes = xpath.queryForNodes(query, iq)
1015        self.assertNotIdentical(None, nodes, 'Bad configure request')
1016
1017        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
1018        self.assertNotIdentical(None, form, 'Missing configuration form')
1019        self.assertEquals('submit', form.formType)
1020
1021        response = toResponse(iq, 'result')
1022        self.stub.send(response)
1023        return d
1024
1025
1026    def test_configureCancel(self):
1027        """
1028        Cancelling room configuration should send a cancel form.
1029        """
1030
1031        d = self.protocol.configure(self.roomJID, None)
1032
1033        iq = self.stub.output[-1]
1034
1035        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
1036        nodes = xpath.queryForNodes(query, iq)
1037
1038        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
1039        self.assertNotIdentical(None, form, 'Missing configuration form')
1040        self.assertEquals('cancel', form.formType)
1041
1042        response = toResponse(iq, 'result')
1043        self.stub.send(response)
1044        return d
1045
1046
1047    def test_getMemberList(self):
1048        """
1049        Retrieving the member list returns a list of L{muc.AdminItem}s
1050
1051        The request asks for the affiliation C{'member'}.
1052        """
1053        def cb(items):
1054            self.assertEquals(1, len(items))
1055            item = items[0]
1056            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1057            self.assertEquals(u'thirdwitch', item.nick)
1058            self.assertEquals(u'member', item.affiliation)
1059
1060        d = self.protocol.getMemberList(self.roomJID)
1061        d.addCallback(cb)
1062
1063        iq = self.stub.output[-1]
1064        self.assertEquals('get', iq.getAttribute('type'))
1065        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1066                                                              muc.NS_MUC_ADMIN)
1067        items = xpath.queryForNodes(query, iq)
1068        self.assertNotIdentical(None, items)
1069        self.assertEquals(1, len(items))
1070        self.assertEquals('member', items[0].getAttribute('affiliation'))
1071
1072        response = toResponse(iq, 'result')
1073        query = response.addElement((NS_MUC_ADMIN, 'query'))
1074        item = query.addElement('item')
1075        item['affiliation'] ='member'
1076        item['jid'] = 'hag66@shakespeare.lit'
1077        item['nick'] = 'thirdwitch'
1078        item['role'] = 'participant'
1079        self.stub.send(response)
1080
1081        return d
1082
1083
1084    def test_getAdminList(self):
1085        """
1086        Retrieving the admin list returns a list of L{muc.AdminItem}s
1087
1088        The request asks for the affiliation C{'admin'}.
1089        """
1090        d = self.protocol.getAdminList(self.roomJID)
1091
1092        iq = self.stub.output[-1]
1093        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1094                                                              muc.NS_MUC_ADMIN)
1095        items = xpath.queryForNodes(query, iq)
1096        self.assertEquals('admin', items[0].getAttribute('affiliation'))
1097
1098        response = toResponse(iq, 'result')
1099        query = response.addElement((NS_MUC_ADMIN, 'query'))
1100        self.stub.send(response)
1101
1102        return d
1103
1104
1105    def test_getBanList(self):
1106        """
1107        Retrieving the ban list returns a list of L{muc.AdminItem}s
1108
1109        The request asks for the affiliation C{'outcast'}.
1110        """
1111        def cb(items):
1112            self.assertEquals(1, len(items))
1113            item = items[0]
1114            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1115            self.assertEquals(u'outcast', item.affiliation)
1116            self.assertEquals(u'Trouble making', item.reason)
1117
1118        d = self.protocol.getBanList(self.roomJID)
1119        d.addCallback(cb)
1120
1121        iq = self.stub.output[-1]
1122        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1123                                                              muc.NS_MUC_ADMIN)
1124        items = xpath.queryForNodes(query, iq)
1125        self.assertEquals('outcast', items[0].getAttribute('affiliation'))
1126
1127        response = toResponse(iq, 'result')
1128        query = response.addElement((NS_MUC_ADMIN, 'query'))
1129        item = query.addElement('item')
1130        item['affiliation'] ='outcast'
1131        item['jid'] = 'hag66@shakespeare.lit'
1132        item.addElement('reason', content='Trouble making')
1133        self.stub.send(response)
1134
1135        return d
1136
1137
1138    def test_getOwnerList(self):
1139        """
1140        Retrieving the owner list returns a list of L{muc.AdminItem}s
1141
1142        The request asks for the affiliation C{'owner'}.
1143        """
1144        d = self.protocol.getOwnerList(self.roomJID)
1145
1146        iq = self.stub.output[-1]
1147        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1148                                                              muc.NS_MUC_ADMIN)
1149        items = xpath.queryForNodes(query, iq)
1150        self.assertEquals('owner', items[0].getAttribute('affiliation'))
1151
1152        response = toResponse(iq, 'result')
1153        query = response.addElement((NS_MUC_ADMIN, 'query'))
1154        self.stub.send(response)
1155
1156        return d
1157
1158
1159    def test_getModeratorList(self):
1160        """
1161        Retrieving the moderator returns a list of L{muc.AdminItem}s.
1162
1163        The request asks for the role C{'moderator'}.
1164        """
1165
1166        def cb(items):
1167            self.assertEquals(1, len(items))
1168            item = items[0]
1169            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
1170            self.assertEquals(u'thirdwitch', item.nick)
1171            self.assertEquals(u'moderator', item.role)
1172
1173        d = self.protocol.getModeratorList(self.roomJID)
1174        d.addCallback(cb)
1175
1176        iq = self.stub.output[-1]
1177        self.assertEquals('get', iq.getAttribute('type'))
1178        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1179                                                              muc.NS_MUC_ADMIN)
1180        items = xpath.queryForNodes(query, iq)
1181        self.assertNotIdentical(None, items)
1182        self.assertEquals(1, len(items))
1183        self.assertEquals('moderator', items[0].getAttribute('role'))
1184
1185        response = toResponse(iq, 'result')
1186        query = response.addElement((NS_MUC_ADMIN, 'query'))
1187        item = query.addElement('item')
1188        item['affiliation'] ='member'
1189        item['jid'] = 'hag66@shakespeare.lit'
1190        item['nick'] = 'thirdwitch'
1191        item['role'] = 'moderator'
1192        self.stub.send(response)
1193
1194        return d
1195
1196
1197    def test_modifyAffiliationList(self):
1198
1199        entities = [JID('user1@test.example.org'),
1200                    JID('user2@test.example.org')]
1201        d = self.protocol.modifyAffiliationList(self.roomJID, entities,
1202                                                'admin')
1203
1204        iq = self.stub.output[-1]
1205        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
1206                                                              muc.NS_MUC_ADMIN)
1207        items = xpath.queryForNodes(query, iq)
1208        self.assertNotIdentical(None, items)
1209        self.assertEquals(entities[0], JID(items[0].getAttribute('jid')))
1210        self.assertEquals('admin', items[0].getAttribute('affiliation'))
1211        self.assertEquals(entities[1], JID(items[1].getAttribute('jid')))
1212        self.assertEquals('admin', items[1].getAttribute('affiliation'))
1213
1214        # Send a response to have the deferred fire.
1215        response = toResponse(iq, 'result')
1216        self.stub.send(response)
1217        return d
1218
1219
1220    def test_grantVoice(self):
1221        """
1222        Granting voice sends request to set role to 'participant'.
1223        """
1224        nick = 'TroubleMaker'
1225        def cb(give_voice):
1226            self.assertTrue(give_voice, 'Did not give voice user')
1227
1228        d = self.protocol.grantVoice(self.roomJID, nick,
1229                                     sender=self.userJID)
1230        d.addCallback(cb)
1231
1232        iq = self.stub.output[-1]
1233
1234        query = (u"/iq[@type='set' and @to='%s']/query/item"
1235                     "[@role='participant']") % self.roomJID
1236        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1237
1238        response = toResponse(iq, 'result')
1239        self.stub.send(response)
1240        return d
1241
1242
1243    def test_revokeVoice(self):
1244        """
1245        Revoking voice sends request to set role to 'visitor'.
1246        """
1247        nick = 'TroubleMaker'
1248
1249        d = self.protocol.revokeVoice(self.roomJID, nick,
1250                                      reason="Trouble maker",
1251                                      sender=self.userJID)
1252
1253        iq = self.stub.output[-1]
1254
1255        query = (u"/iq[@type='set' and @to='%s']/query/item"
1256                     "[@role='visitor']") % self.roomJID
1257        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1258
1259        response = toResponse(iq, 'result')
1260        self.stub.send(response)
1261        return d
1262
1263
1264    def test_grantModerator(self):
1265        """
1266        Granting moderator privileges sends request to set role to 'moderator'.
1267        """
1268        nick = 'TroubleMaker'
1269
1270        d = self.protocol.grantModerator(self.roomJID, nick,
1271                                         sender=self.userJID)
1272
1273        iq = self.stub.output[-1]
1274
1275        query = (u"/iq[@type='set' and @to='%s']/query/item"
1276                     "[@role='moderator']") % self.roomJID
1277        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1278
1279        response = toResponse(iq, 'result')
1280        self.stub.send(response)
1281        return d
1282
1283
1284    def test_ban(self):
1285        """
1286        Ban an entity in a room.
1287        """
1288        banned = JID('ban@jabber.org/TroubleMaker')
1289
1290        def cb(banned):
1291            self.assertTrue(banned, 'Did not ban user')
1292
1293        d = self.protocol.ban(self.roomJID, banned, reason='Spam',
1294                              sender=self.userJID)
1295        d.addCallback(cb)
1296
1297        iq = self.stub.output[-1]
1298
1299        self.assertTrue(xpath.matches(
1300                u"/iq[@type='set' and @to='%s']/query/item"
1301                    "[@affiliation='outcast']" % (self.roomJID,),
1302                iq),
1303            'Wrong ban stanza')
1304
1305        response = toResponse(iq, 'result')
1306        self.stub.send(response)
1307
1308        return d
1309
1310
1311    def test_kick(self):
1312        """
1313        Kick an entity from a room.
1314        """
1315        nick = 'TroubleMaker'
1316
1317        def cb(kicked):
1318            self.assertTrue(kicked, 'Did not kick user')
1319
1320        d = self.protocol.kick(self.roomJID, nick, reason='Spam',
1321                               sender=self.userJID)
1322        d.addCallback(cb)
1323
1324        iq = self.stub.output[-1]
1325
1326        self.assertTrue(xpath.matches(
1327                u"/iq[@type='set' and @to='%s']/query/item"
1328                    "[@role='none']" % (self.roomJID,),
1329                iq),
1330            'Wrong kick stanza')
1331
1332        response = toResponse(iq, 'result')
1333        self.stub.send(response)
1334
1335        return d
1336
1337
1338    def test_destroy(self):
1339        """
1340        Destroy a room.
1341        """
1342        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1343                                  alternate=JID('other@%s' % self.service),
1344                                  password='secret')
1345
1346        iq = self.stub.output[-1]
1347
1348        query = ("/iq/query[@xmlns='%s']/destroy[@xmlns='%s']" %
1349                 (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER))
1350
1351        nodes = xpath.queryForNodes(query, iq)
1352        self.assertNotIdentical(None, nodes, 'Bad configure request')
1353        destroy = nodes[0]
1354        self.assertEquals('Time to leave', unicode(destroy.reason))
1355
1356        response = toResponse(iq, 'result')
1357        self.stub.send(response)
1358        return d
1359
1360
1361
1362class MUCClientTest(unittest.TestCase):
1363    """
1364    Tests for C{muc.MUCClient}.
1365    """
1366
1367    def setUp(self):
1368        self.clock = task.Clock()
1369        self.sessionManager = TestableStreamManager(reactor=self.clock)
1370        self.stub = self.sessionManager.stub
1371        self.protocol = muc.MUCClient(reactor=self.clock)
1372        self.protocol.setHandlerParent(self.sessionManager)
1373
1374        self.roomIdentifier = 'test'
1375        self.service  = 'conference.example.org'
1376        self.nick = 'Nick'
1377
1378        self.occupantJID = JID(tuple=(self.roomIdentifier,
1379                                      self.service,
1380                                      self.nick))
1381        self.roomJID = self.occupantJID.userhostJID()
1382        self.userJID = JID('test@example.org/Testing')
1383
1384
1385    def _createRoom(self):
1386        """
1387        A helper method to create a test room.
1388        """
1389        # create a room
1390        room = muc.Room(self.roomJID, self.nick)
1391        self.protocol._addRoom(room)
1392        return room
1393
1394
1395    def test_interface(self):
1396        """
1397        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
1398        """
1399        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
1400
1401
1402    def _testPresence(self, sender='', available=True):
1403        """
1404        Helper for presence tests.
1405        """
1406        def userUpdatedStatus(room, user, show, status):
1407            self.fail("Unexpected call to userUpdatedStatus")
1408
1409        def userJoinedRoom(room, user):
1410            self.fail("Unexpected call to userJoinedRoom")
1411
1412        if available:
1413            available = ""
1414        else:
1415            available = " type='unavailable'"
1416
1417        if sender:
1418            sender = u" from='%s'" % sender
1419
1420        xml = u"""
1421            <presence to='%s'%s%s>
1422              <x xmlns='http://jabber.org/protocol/muc#user'>
1423                <item affiliation='member' role='participant'/>
1424              </x>
1425            </presence>
1426        """ % (self.userJID, sender, available)
1427
1428        self.protocol.userUpdatedStatus = userUpdatedStatus
1429        self.protocol.userJoinedRoom = userJoinedRoom
1430        self.stub.send(parseXml(xml))
1431
1432
1433    def test_availableReceivedEmptySender(self):
1434        """
1435        Availability presence from empty sender is ignored.
1436        """
1437        self._testPresence(sender='')
1438
1439
1440    def test_availableReceivedNotInRoom(self):
1441        """
1442        Availability presence from unknown entities is ignored.
1443        """
1444        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1445        self._testPresence(sender=otherOccupantJID)
1446
1447
1448    def test_unavailableReceivedEmptySender(self):
1449        """
1450        Availability presence from empty sender is ignored.
1451        """
1452        self._testPresence(sender='', available=False)
1453
1454
1455    def test_unavailableReceivedNotInRoom(self):
1456        """
1457        Availability presence from unknown entities is ignored.
1458        """
1459        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1460        self._testPresence(sender=otherOccupantJID, available=False)
1461
1462
1463    def test_unavailableReceivedNotInRoster(self):
1464        """
1465        Availability presence from unknown entities is ignored.
1466        """
1467        room = self._createRoom()
1468        user = muc.User(self.nick)
1469        room.addUser(user)
1470        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
1471        self._testPresence(sender=otherOccupantJID, available=False)
1472
1473
1474    def test_userJoinedRoom(self):
1475        """
1476        Joins by others to a room we're in are passed to userJoinedRoom
1477        """
1478        xml = """
1479            <presence to='%s' from='%s'>
1480              <x xmlns='http://jabber.org/protocol/muc#user'>
1481                <item affiliation='member' role='participant'/>
1482              </x>
1483            </presence>
1484        """ % (self.userJID.full(), self.occupantJID.full())
1485
1486        # create a room
1487        self._createRoom()
1488
1489        def userJoinedRoom(room, user):
1490            self.assertEquals(self.roomJID, room.roomJID,
1491                              'Wrong room name')
1492            self.assertTrue(room.inRoster(user), 'User not in roster')
1493
1494        d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom)
1495        self.stub.send(parseXml(xml))
1496        return d
1497
1498
1499    def test_receivedSubject(self):
1500        """
1501        Subject received from a room we're in are passed to receivedSubject.
1502        """
1503        xml = u"""
1504            <message to='%s' from='%s' type='groupchat'>
1505              <subject>test</subject>
1506            </message>
1507        """ % (self.userJID, self.occupantJID)
1508
1509        self._createRoom()
1510
1511        # add user to room
1512        user = muc.User(self.nick)
1513        room = self.protocol._getRoom(self.roomJID)
1514        room.addUser(user)
1515
1516        def receivedSubject(room, user, subject):
1517            self.assertEquals('test', subject, "Wrong group chat message")
1518            self.assertEquals(self.roomJID, room.roomJID,
1519                              'Wrong room name')
1520            self.assertEquals(self.nick, user.nick)
1521
1522        d, self.protocol.receivedSubject = calledAsync(receivedSubject)
1523        self.stub.send(parseXml(xml))
1524        return d
1525
1526
1527    def test_receivedSubjectNotOverridden(self):
1528        """
1529        Not overriding receivedSubject is ok.
1530        """
1531        xml = u"""
1532            <message to='%s' from='%s' type='groupchat'>
1533              <subject>test</subject>
1534            </message>
1535        """ % (self.userJID, self.occupantJID)
1536
1537        self._createRoom()
1538        self.stub.send(parseXml(xml))
1539
1540
1541    def test_receivedGroupChat(self):
1542        """
1543        Messages received from a room we're in are passed to receivedGroupChat.
1544        """
1545        xml = u"""
1546            <message to='test@test.com' from='%s' type='groupchat'>
1547              <body>test</body>
1548            </message>
1549        """ % (self.occupantJID)
1550
1551        self._createRoom()
1552
1553        def receivedGroupChat(room, user, message):
1554            self.assertEquals('test', message.body, "Wrong group chat message")
1555            self.assertEquals(self.roomJID, room.roomJID,
1556                              'Wrong room name')
1557
1558        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1559        self.stub.send(parseXml(xml))
1560        return d
1561
1562
1563    def test_receivedGroupChatRoom(self):
1564        """
1565        Messages received from the room itself have C{user} set to C{None}.
1566        """
1567        xml = u"""
1568            <message to='test@test.com' from='%s' type='groupchat'>
1569              <body>test</body>
1570            </message>
1571        """ % (self.roomJID)
1572
1573        self._createRoom()
1574
1575        def receivedGroupChat(room, user, message):
1576            self.assertIdentical(None, user)
1577
1578        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1579        self.stub.send(parseXml(xml))
1580        return d
1581
1582
1583    def test_receivedGroupChatNotInRoom(self):
1584        """
1585        Messages received from a room we're not in are ignored.
1586        """
1587        xml = u"""
1588            <message to='test@test.com' from='%s' type='groupchat'>
1589              <body>test</body>
1590            </message>
1591        """ % (self.occupantJID)
1592
1593        def receivedGroupChat(room, user, message):
1594            self.fail("Unexpected call to receivedGroupChat")
1595
1596        self.protocol.receivedGroupChat = receivedGroupChat
1597        self.stub.send(parseXml(xml))
1598
1599
1600    def test_receivedGroupChatNotOverridden(self):
1601        """
1602        Not overriding receivedGroupChat is ok.
1603        """
1604        xml = u"""
1605            <message to='test@test.com' from='%s' type='groupchat'>
1606              <body>test</body>
1607            </message>
1608        """ % (self.occupantJID)
1609
1610        self._createRoom()
1611        self.stub.send(parseXml(xml))
1612
1613
1614    def test_join(self):
1615        """
1616        Joining a room waits for confirmation, deferred fires room.
1617        """
1618
1619        def cb(room):
1620            self.assertEqual(self.roomJID, room.roomJID)
1621            self.assertTrue('joined', room.state)
1622
1623        d = self.protocol.join(self.roomJID, self.nick)
1624        d.addCallback(cb)
1625
1626        # send back user presence, they joined
1627        xml = """
1628            <presence from='%s@%s/%s'>
1629              <x xmlns='http://jabber.org/protocol/muc#user'>
1630                <item affiliation='member' role='participant'/>
1631              </x>
1632            </presence>
1633        """ % (self.roomIdentifier, self.service, self.nick)
1634        self.stub.send(parseXml(xml))
1635        return d
1636
1637
1638    def test_joinForbidden(self):
1639        """
1640        A forbidden error in response to a join errbacks with L{StanzaError}.
1641        """
1642
1643        def cb(error):
1644            self.assertEquals('forbidden', error.condition,
1645                              'Wrong muc condition')
1646            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1647
1648
1649        d = self.protocol.join(self.roomJID, self.nick)
1650        self.assertFailure(d, StanzaError)
1651        d.addCallback(cb)
1652
1653        # send back error, forbidden
1654        xml = u"""
1655            <presence from='%s' type='error'>
1656              <error type='auth'>
1657                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1658              </error>
1659            </presence>
1660        """ % (self.occupantJID)
1661        self.stub.send(parseXml(xml))
1662        return d
1663
1664
1665    def test_userLeftRoom(self):
1666        """
1667        Unavailable presence from a participant removes it from the room.
1668        """
1669
1670        xml = u"""
1671            <presence to='%s' from='%s' type='unavailable'/>
1672        """ % (self.userJID, self.occupantJID)
1673
1674        # create a room
1675        self._createRoom()
1676
1677        # add user to room
1678        user = muc.User(self.nick)
1679        room = self.protocol._getRoom(self.roomJID)
1680        room.addUser(user)
1681
1682        def userLeftRoom(room, user):
1683            self.assertEquals(self.roomJID, room.roomJID,
1684                              'Wrong room name')
1685            self.assertFalse(room.inRoster(user), 'User in roster')
1686
1687        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
1688        self.stub.send(parseXml(xml))
1689        return d
1690
1691
1692    def test_receivedHistory(self):
1693        """
1694        Receiving history on room join.
1695        """
1696        xml = u"""
1697            <message to='test@test.com' from='%s' type='groupchat'>
1698              <body>test</body>
1699              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1700                                            from="%s"/>
1701            </message>
1702        """ % (self.occupantJID, self.userJID)
1703
1704        self._createRoom()
1705
1706
1707        def receivedHistory(room, user, message):
1708            self.assertEquals('test', message.body, "wrong message body")
1709            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
1710            self.assertEquals(stamp, message.delay.stamp,
1711                             'Does not have a history stamp')
1712
1713        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
1714        self.stub.send(parseXml(xml))
1715        return d
1716
1717
1718    def test_receivedHistoryNotOverridden(self):
1719        """
1720        Not overriding receivedHistory is ok.
1721        """
1722        xml = u"""
1723            <message to='test@test.com' from='%s' type='groupchat'>
1724              <body>test</body>
1725              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1726                                            from="%s"/>
1727            </message>
1728        """ % (self.occupantJID, self.userJID)
1729
1730        self._createRoom()
1731        self.stub.send(parseXml(xml))
1732
1733
1734    def test_nickConflict(self):
1735        """
1736        If the server finds the new nick in conflict, the errback is called.
1737        """
1738
1739        def cb(failure, room):
1740            user = room.getUser(otherNick)
1741            self.assertNotIdentical(None, user)
1742            self.assertEqual(otherJID, user.entity)
1743
1744        def joined(room):
1745            d = self.protocol.nick(room.roomJID, otherNick)
1746            self.assertFailure(d, StanzaError)
1747            d.addCallback(cb, room)
1748
1749        otherJID = JID('other@example.org/Home')
1750        otherNick = 'otherNick'
1751
1752        d = self.protocol.join(self.roomJID, self.nick)
1753        d.addCallback(joined)
1754
1755        # Send back other partipant's presence.
1756        xml = u"""
1757            <presence from='%s/%s'>
1758              <x xmlns='http://jabber.org/protocol/muc#user'>
1759                <item affiliation='member' role='participant' jid='%s'/>
1760              </x>
1761            </presence>
1762        """ % (self.roomJID, otherNick, otherJID)
1763        self.stub.send(parseXml(xml))
1764
1765        # send back user presence, they joined
1766        xml = u"""
1767            <presence from='%s/%s'>
1768              <x xmlns='http://jabber.org/protocol/muc#user'>
1769                <item affiliation='member' role='participant'/>
1770              </x>
1771            </presence>
1772        """ % (self.roomJID, self.nick)
1773        self.stub.send(parseXml(xml))
1774
1775        # send back error presence, nick conflicted
1776        xml = u"""
1777            <presence from='%s/%s' type='error'>
1778                <x xmlns='http://jabber.org/protocol/muc'/>
1779                <error type='cancel'>
1780                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1781                </error>
1782            </presence>
1783        """ % (self.roomJID, otherNick)
1784        self.stub.send(parseXml(xml))
1785        return d
1786
1787
1788    def test_nick(self):
1789        """
1790        Send a nick change to the server.
1791        """
1792        newNick = 'newNick'
1793
1794        room = self._createRoom()
1795
1796        def joined(room):
1797            self.assertEqual(self.roomJID, room.roomJID)
1798            self.assertEqual(newNick, room.nick)
1799            user = room.getUser(newNick)
1800            self.assertNotIdentical(None, user)
1801            self.assertEqual(newNick, user.nick)
1802
1803        d = self.protocol.nick(self.roomJID, newNick)
1804        d.addCallback(joined)
1805
1806        # Nick should not have been changed, yet, as we haven't gotten
1807        # confirmation, yet.
1808
1809        self.assertEquals(self.nick, room.nick)
1810
1811        # send back user presence, nick changed
1812        xml = u"""
1813            <presence from='%s/%s'>
1814              <x xmlns='http://jabber.org/protocol/muc#user'>
1815                <item affiliation='member' role='participant'/>
1816              </x>
1817            </presence>
1818        """ % (self.roomJID, newNick)
1819
1820        self.stub.send(parseXml(xml))
1821        return d
1822
1823
1824    def test_leave(self):
1825        """
1826        Client leaves a room
1827        """
1828        def joined(_):
1829            return self.protocol.leave(self.roomJID)
1830
1831        def left(_):
1832            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1833
1834        # Join the room
1835        d = self.protocol.join(self.roomJID, self.nick)
1836        d.addCallback(joined)
1837        d.addCallback(left)
1838
1839        # Receive presence back from the room: joined.
1840        xml = u"""
1841            <presence to='%s' from='%s'/>
1842        """ % (self.userJID, self.occupantJID)
1843        self.stub.send(parseXml(xml))
1844
1845        # Receive presence back from the room: left.
1846        xml = u"""
1847            <presence to='%s' from='%s' type='unavailable'/>
1848        """ % (self.userJID, self.occupantJID)
1849        self.stub.send(parseXml(xml))
1850
1851        return d
1852
1853
1854    def test_status(self):
1855        """
1856        Change status
1857        """
1858        def joined(_):
1859            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
1860            d.addCallback(statusChanged)
1861            return d
1862
1863        def statusChanged(room):
1864            self.assertEqual(self.roomJID, room.roomJID)
1865            user = room.getUser(self.nick)
1866            self.assertNotIdentical(None, user, 'User not found')
1867            self.assertEqual('testing MUC', user.status, 'Wrong status')
1868            self.assertEqual('xa', user.show, 'Wrong show')
1869
1870        # Join the room
1871        d = self.protocol.join(self.roomJID, self.nick)
1872        d.addCallback(joined)
1873
1874        # Receive presence back from the room: joined.
1875        xml = u"""
1876            <presence to='%s' from='%s'/>
1877        """ % (self.userJID, self.occupantJID)
1878        self.stub.send(parseXml(xml))
1879
1880        # send back user presence, status changed
1881        xml = u"""
1882            <presence from='%s'>
1883              <x xmlns='http://jabber.org/protocol/muc#user'>
1884                <item affiliation='member' role='participant'/>
1885              </x>
1886              <show>xa</show>
1887              <status>testing MUC</status>
1888            </presence>
1889        """ % self.occupantJID
1890
1891        self.stub.send(parseXml(xml))
1892        return d
1893
1894
1895    def test_destroy(self):
1896        """
1897        Destroy a room.
1898        """
1899        def destroyed(_):
1900            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1901
1902        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1903                                  alternate=JID('other@%s' % self.service),
1904                                  password='secret')
1905        d.addCallback(destroyed)
1906
1907        iq = self.stub.output[-1]
1908        response = toResponse(iq, 'result')
1909        self.stub.send(response)
1910        return d
Note: See TracBrowser for help on using the repository browser.