source: wokkel/test/test_muc.py @ 161:aecf77f1bb1f

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

Allow empty MUC configuration forms to be sent.

File size: 60.3 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/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.assertTrue('joined', room.state)
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_joinForbidden(self):
1684        """
1685        A forbidden error in response to a join errbacks with L{StanzaError}.
1686        """
1687
1688        def cb(error):
1689            self.assertEquals('forbidden', error.condition,
1690                              'Wrong muc condition')
1691            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1692
1693
1694        d = self.protocol.join(self.roomJID, self.nick)
1695        self.assertFailure(d, StanzaError)
1696        d.addCallback(cb)
1697
1698        # send back error, forbidden
1699        xml = u"""
1700            <presence from='%s' type='error'>
1701              <error type='auth'>
1702                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1703              </error>
1704            </presence>
1705        """ % (self.occupantJID)
1706        self.stub.send(parseXml(xml))
1707        return d
1708
1709
1710    def test_userLeftRoom(self):
1711        """
1712        Unavailable presence from a participant removes it from the room.
1713        """
1714
1715        xml = u"""
1716            <presence to='%s' from='%s' type='unavailable'/>
1717        """ % (self.userJID, self.occupantJID)
1718
1719        # create a room
1720        self._createRoom()
1721
1722        # add user to room
1723        user = muc.User(self.nick)
1724        room = self.protocol._getRoom(self.roomJID)
1725        room.addUser(user)
1726
1727        def userLeftRoom(room, user):
1728            self.assertEquals(self.roomJID, room.roomJID,
1729                              'Wrong room name')
1730            self.assertFalse(room.inRoster(user), 'User in roster')
1731
1732        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
1733        self.stub.send(parseXml(xml))
1734        return d
1735
1736
1737    def test_receivedHistory(self):
1738        """
1739        Receiving history on room join.
1740        """
1741        xml = u"""
1742            <message to='test@test.com' from='%s' type='groupchat'>
1743              <body>test</body>
1744              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1745                                            from="%s"/>
1746            </message>
1747        """ % (self.occupantJID, self.userJID)
1748
1749        self._createRoom()
1750
1751
1752        def receivedHistory(room, user, message):
1753            self.assertEquals('test', message.body, "wrong message body")
1754            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
1755            self.assertEquals(stamp, message.delay.stamp,
1756                             'Does not have a history stamp')
1757
1758        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
1759        self.stub.send(parseXml(xml))
1760        return d
1761
1762
1763    def test_receivedHistoryNotOverridden(self):
1764        """
1765        Not overriding receivedHistory is ok.
1766        """
1767        xml = u"""
1768            <message to='test@test.com' from='%s' type='groupchat'>
1769              <body>test</body>
1770              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1771                                            from="%s"/>
1772            </message>
1773        """ % (self.occupantJID, self.userJID)
1774
1775        self._createRoom()
1776        self.stub.send(parseXml(xml))
1777
1778
1779    def test_nickConflict(self):
1780        """
1781        If the server finds the new nick in conflict, the errback is called.
1782        """
1783
1784        def cb(failure, room):
1785            user = room.getUser(otherNick)
1786            self.assertNotIdentical(None, user)
1787            self.assertEqual(otherJID, user.entity)
1788
1789        def joined(room):
1790            d = self.protocol.nick(room.roomJID, otherNick)
1791            self.assertFailure(d, StanzaError)
1792            d.addCallback(cb, room)
1793
1794        otherJID = JID('other@example.org/Home')
1795        otherNick = 'otherNick'
1796
1797        d = self.protocol.join(self.roomJID, self.nick)
1798        d.addCallback(joined)
1799
1800        # Send back other partipant's presence.
1801        xml = u"""
1802            <presence from='%s/%s'>
1803              <x xmlns='http://jabber.org/protocol/muc#user'>
1804                <item affiliation='member' role='participant' jid='%s'/>
1805              </x>
1806            </presence>
1807        """ % (self.roomJID, otherNick, otherJID)
1808        self.stub.send(parseXml(xml))
1809
1810        # send back user presence, they joined
1811        xml = u"""
1812            <presence from='%s/%s'>
1813              <x xmlns='http://jabber.org/protocol/muc#user'>
1814                <item affiliation='member' role='participant'/>
1815              </x>
1816            </presence>
1817        """ % (self.roomJID, self.nick)
1818        self.stub.send(parseXml(xml))
1819
1820        # send back error presence, nick conflicted
1821        xml = u"""
1822            <presence from='%s/%s' type='error'>
1823                <x xmlns='http://jabber.org/protocol/muc'/>
1824                <error type='cancel'>
1825                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
1826                </error>
1827            </presence>
1828        """ % (self.roomJID, otherNick)
1829        self.stub.send(parseXml(xml))
1830        return d
1831
1832
1833    def test_nick(self):
1834        """
1835        Send a nick change to the server.
1836        """
1837        newNick = 'newNick'
1838
1839        room = self._createRoom()
1840
1841        def joined(room):
1842            self.assertEqual(self.roomJID, room.roomJID)
1843            self.assertEqual(newNick, room.nick)
1844            user = room.getUser(newNick)
1845            self.assertNotIdentical(None, user)
1846            self.assertEqual(newNick, user.nick)
1847
1848        d = self.protocol.nick(self.roomJID, newNick)
1849        d.addCallback(joined)
1850
1851        # Nick should not have been changed, yet, as we haven't gotten
1852        # confirmation, yet.
1853
1854        self.assertEquals(self.nick, room.nick)
1855
1856        # send back user presence, nick changed
1857        xml = u"""
1858            <presence from='%s/%s'>
1859              <x xmlns='http://jabber.org/protocol/muc#user'>
1860                <item affiliation='member' role='participant'/>
1861              </x>
1862            </presence>
1863        """ % (self.roomJID, newNick)
1864
1865        self.stub.send(parseXml(xml))
1866        return d
1867
1868
1869    def test_leave(self):
1870        """
1871        Client leaves a room
1872        """
1873        def joined(_):
1874            return self.protocol.leave(self.roomJID)
1875
1876        def left(_):
1877            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1878
1879        # Join the room
1880        d = self.protocol.join(self.roomJID, self.nick)
1881        d.addCallback(joined)
1882        d.addCallback(left)
1883
1884        # Receive presence back from the room: joined.
1885        xml = u"""
1886            <presence to='%s' from='%s'/>
1887        """ % (self.userJID, self.occupantJID)
1888        self.stub.send(parseXml(xml))
1889
1890        # Receive presence back from the room: left.
1891        xml = u"""
1892            <presence to='%s' from='%s' type='unavailable'/>
1893        """ % (self.userJID, self.occupantJID)
1894        self.stub.send(parseXml(xml))
1895
1896        return d
1897
1898
1899    def test_status(self):
1900        """
1901        Change status
1902        """
1903        def joined(_):
1904            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
1905            d.addCallback(statusChanged)
1906            return d
1907
1908        def statusChanged(room):
1909            self.assertEqual(self.roomJID, room.roomJID)
1910            user = room.getUser(self.nick)
1911            self.assertNotIdentical(None, user, 'User not found')
1912            self.assertEqual('testing MUC', user.status, 'Wrong status')
1913            self.assertEqual('xa', user.show, 'Wrong show')
1914
1915        # Join the room
1916        d = self.protocol.join(self.roomJID, self.nick)
1917        d.addCallback(joined)
1918
1919        # Receive presence back from the room: joined.
1920        xml = u"""
1921            <presence to='%s' from='%s'/>
1922        """ % (self.userJID, self.occupantJID)
1923        self.stub.send(parseXml(xml))
1924
1925        # send back user presence, status changed
1926        xml = u"""
1927            <presence from='%s'>
1928              <x xmlns='http://jabber.org/protocol/muc#user'>
1929                <item affiliation='member' role='participant'/>
1930              </x>
1931              <show>xa</show>
1932              <status>testing MUC</status>
1933            </presence>
1934        """ % self.occupantJID
1935
1936        self.stub.send(parseXml(xml))
1937        return d
1938
1939
1940    def test_destroy(self):
1941        """
1942        Destroy a room.
1943        """
1944        def destroyed(_):
1945            self.assertIdentical(None, self.protocol._getRoom(self.roomJID))
1946
1947        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1948                                  alternate=JID('other@%s' % self.service),
1949                                  password='secret')
1950        d.addCallback(destroyed)
1951
1952        iq = self.stub.output[-1]
1953        response = toResponse(iq, 'result')
1954        self.stub.send(response)
1955        return d
Note: See TracBrowser for help on using the repository browser.