source: wokkel/test/test_muc.py @ 153:bf4b940f3547

wokkel-muc-client-support-24
Last change on this file since 153:bf4b940f3547 was 153:bf4b940f3547, checked in by Ralph Meijer <ralphm@…>, 9 years ago

Improve UserPresence? parsing, clean up MUCClient, approach full test coverage.

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