source: wokkel/test/test_muc.py @ 152:ae3907d81abc

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

Split protocol and room/user administration into two classes.

This introduces MUCClientProtocol, which only contains the protocol
implementation. The only state keeping there is remembering the Occupant JID
that was used to join a room, so you can use the Room JID everywhere.

The room and user owner administration that was intertwined with the protocol
is still in MUCClient that is a subclass of MUCClientProtocol. As this
record keeping barely had proper tests, the test coverage for this class is
sub-optimal for now.

This also fixes various test cases for the pure protocol support.

File size: 42.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
123
124class MUCClientProtocolTest(unittest.TestCase):
125    """
126    Tests for L{muc.MUCClientProtocol}.
127    """
128
129    def setUp(self):
130        self.clock = task.Clock()
131        self.sessionManager = TestableStreamManager(reactor=self.clock)
132        self.stub = self.sessionManager.stub
133        self.protocol = muc.MUCClientProtocol(reactor=self.clock)
134        self.protocol.setHandlerParent(self.sessionManager)
135
136        self.roomIdentifier = 'test'
137        self.service  = 'conference.example.org'
138        self.nick = 'Nick'
139
140        self.occupantJID = JID(tuple=(self.roomIdentifier,
141                                      self.service,
142                                      self.nick))
143        self.roomJID = self.occupantJID.userhostJID()
144        self.userJID = JID('test@example.org/Testing')
145
146
147    def test_initNoReactor(self):
148        """
149        If no reactor is passed, the default reactor is used.
150        """
151        protocol = muc.MUCClientProtocol()
152        from twisted.internet import reactor
153        self.assertEqual(reactor, protocol._reactor)
154
155
156    def test_groupChatReceived(self):
157        """
158        Messages of type groupchat are parsed and passed to L{groupChatReceived}.
159        """
160        xml = u"""
161            <message to='test@test.com' from='%s' type='groupchat'>
162              <body>test</body>
163            </message>
164        """ % (self.occupantJID)
165
166        def groupChatReceived(message):
167            self.assertEquals('test', message.body, "Wrong group chat message")
168            self.assertEquals(self.roomIdentifier, message.sender.user,
169                              'Wrong room identifier')
170
171        d, self.protocol.groupChatReceived = calledAsync(groupChatReceived)
172        self.stub.send(parseXml(xml))
173        return d
174
175
176    def test_groupChatReceivedNotOverridden(self):
177        """
178        If L{groupChatReceived} has not been overridden, no errors should occur.
179        """
180        xml = u"""
181            <message to='test@test.com' from='%s' type='groupchat'>
182              <body>test</body>
183            </message>
184        """ % (self.occupantJID)
185
186        self.stub.send(parseXml(xml))
187
188
189    def test_join(self):
190        """
191        Joining a room waits for confirmation, deferred fires user presence.
192        """
193
194        def cb(presence):
195            self.assertEquals(self.occupantJID, presence.sender)
196
197        # Join the room
198        d = self.protocol.join(self.roomJID, self.nick)
199        d.addCallback(cb)
200
201        element = self.stub.output[-1]
202
203        self.assertEquals('presence', element.name, "Need to be presence")
204        self.assertNotIdentical(None, element.x, 'No muc x element')
205
206        # send back user presence, they joined
207        xml = """
208            <presence from='%s@%s/%s'>
209              <x xmlns='http://jabber.org/protocol/muc#user'>
210                <item affiliation='member' role='participant'/>
211              </x>
212            </presence>
213        """ % (self.roomIdentifier, self.service, self.nick)
214        self.stub.send(parseXml(xml))
215        return d
216
217
218    def test_joinHistory(self):
219        """
220        Passing a history parameter sends a 'maxStanzas' history limit.
221        """
222
223        historyOptions = muc.HistoryOptions(maxStanzas=10)
224        d = self.protocol.join(self.roomJID, self.nick,
225                               historyOptions)
226
227        element = self.stub.output[-1]
228        query = "/*/x[@xmlns='%s']/history[@xmlns='%s']" % (muc.NS_MUC,
229                                                            muc.NS_MUC)
230        result = xpath.queryForNodes(query, element)
231        history = result[0]
232        self.assertEquals('10', history.getAttribute('maxstanzas'))
233
234        # send back user presence, they joined
235        xml = """
236            <presence from='%s@%s/%s'>
237              <x xmlns='http://jabber.org/protocol/muc#user'>
238                <item affiliation='member' role='participant'/>
239              </x>
240            </presence>
241        """ % (self.roomIdentifier, self.service, self.nick)
242        self.stub.send(parseXml(xml))
243        return d
244
245
246    def test_joinForbidden(self):
247        """
248        A forbidden error in response to a join errbacks with L{StanzaError}.
249        """
250
251        def cb(error):
252            self.assertEquals('forbidden', error.condition,
253                              'Wrong muc condition')
254
255        d = self.protocol.join(self.roomJID, self.nick)
256        self.assertFailure(d, StanzaError)
257        d.addCallback(cb)
258
259        # send back error, forbidden
260        xml = u"""
261            <presence from='%s' type='error'>
262              <error type='auth'>
263                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
264              </error>
265            </presence>
266        """ % (self.occupantJID)
267        self.stub.send(parseXml(xml))
268        return d
269
270
271    def test_joinForbiddenFromRoomJID(self):
272        """
273        An error response to a join sent from the room JID should errback.
274
275        Some service implementations send error stanzas from the room JID
276        instead of the JID the join presence was sent to.
277        """
278
279        d = self.protocol.join(self.roomJID, self.nick)
280        self.assertFailure(d, StanzaError)
281
282        # send back error, forbidden
283        xml = u"""
284            <presence from='%s' type='error'>
285              <error type='auth'>
286                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
287              </error>
288            </presence>
289        """ % (self.roomJID)
290        self.stub.send(parseXml(xml))
291        return d
292
293
294    def test_joinBadJID(self):
295        """
296        Client joining a room and getting a jid-malformed error.
297        """
298
299        def cb(error):
300            self.assertEquals('jid-malformed', error.condition,
301                              'Wrong muc condition')
302
303        d = self.protocol.join(self.roomJID, self.nick)
304        self.assertFailure(d, StanzaError)
305        d.addCallback(cb)
306
307        # send back error, bad JID
308        xml = u"""
309            <presence from='%s' type='error'>
310              <error type='modify'>
311                <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
312              </error>
313            </presence>
314        """ % (self.occupantJID)
315        self.stub.send(parseXml(xml))
316        return d
317
318
319    def test_joinTimeout(self):
320        """
321        After not receiving a response to a join, errback with L{TimeoutError}.
322        """
323
324        d = self.protocol.join(self.roomJID, self.nick)
325        self.assertFailure(d, TimeoutError)
326        self.clock.advance(muc.DEFER_TIMEOUT)
327        return d
328
329
330    def test_joinPassword(self):
331        """
332        Sending a password via presence to a password protected room.
333        """
334
335        self.protocol.join(self.roomJID, self.nick, password='secret')
336
337        element = self.stub.output[-1]
338
339        self.assertTrue(xpath.matches(
340                u"/presence[@to='%s']/x/password"
341                    "[text()='secret']" % (self.occupantJID,),
342                element),
343            'Wrong presence stanza')
344
345
346    def test_nick(self):
347        """
348        Send a nick change to the server.
349        """
350        newNick = 'newNick'
351
352        def cb(presence):
353            self.assertEquals(JID(tuple=(self.roomIdentifier,
354                                         self.service,
355                                         newNick)),
356                              presence.sender)
357
358        d = self.protocol.nick(self.roomJID, newNick)
359        d.addCallback(cb)
360
361        element = self.stub.output[-1]
362        self.assertEquals('presence', element.name, "Need to be presence")
363        self.assertNotIdentical(None, element.x, 'No muc x element')
364
365        # send back user presence, nick changed
366        xml = u"""
367            <presence from='%s/%s'>
368              <x xmlns='http://jabber.org/protocol/muc#user'>
369                <item affiliation='member' role='participant'/>
370              </x>
371            </presence>
372        """ % (self.roomJID, newNick)
373        self.stub.send(parseXml(xml))
374        return d
375
376
377    def test_nickConflict(self):
378        """
379        If the server finds the new nick in conflict, the errback is called.
380        """
381        newNick = 'newNick'
382
383        d = self.protocol.nick(self.roomJID, newNick)
384        self.assertFailure(d, StanzaError)
385
386        element = self.stub.output[-1]
387        self.assertEquals('presence', element.name, "Need to be presence")
388        self.assertNotIdentical(None, element.x, 'No muc x element')
389
390        # send back user presence, nick changed
391        xml = u"""
392            <presence from='%s/%s' type='error'>
393                <x xmlns='http://jabber.org/protocol/muc'/>
394                <error type='cancel'>
395                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
396                </error>
397            </presence>
398        """ % (self.roomJID, newNick)
399        self.stub.send(parseXml(xml))
400        return d
401
402
403    def test_status(self):
404        """
405        Change status
406        """
407        def joined(_):
408            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
409            d.addCallback(statusChanged)
410            return d
411
412        def statusChanged(presence):
413            self.assertEqual(self.occupantJID, presence.sender)
414
415        # Join the room
416        d = self.protocol.join(self.roomJID, self.nick)
417        d.addCallback(joined)
418
419        # Receive presence back from the room: joined.
420        xml = u"""
421            <presence to='%s' from='%s'/>
422        """ % (self.userJID, self.occupantJID)
423        self.stub.send(parseXml(xml))
424
425        # The presence for the status change should have been sent now.
426        element = self.stub.output[-1]
427
428        self.assertEquals('presence', element.name, "Need to be presence")
429        self.assertTrue(getattr(element, 'x', None), 'No muc x element')
430
431        # send back user presence, status changed
432        xml = u"""
433            <presence from='%s'>
434              <x xmlns='http://jabber.org/protocol/muc#user'>
435                <item affiliation='member' role='participant'/>
436              </x>
437              <show>xa</show>
438              <status>testing MUC</status>
439            </presence>
440        """ % self.occupantJID
441        self.stub.send(parseXml(xml))
442
443        return d
444
445
446    def test_leave(self):
447        """
448        Client leaves a room
449        """
450        def joined(_):
451            return self.protocol.leave(self.roomJID)
452
453        # Join the room
454        d = self.protocol.join(self.roomJID, self.nick)
455        d.addCallback(joined)
456
457        # Receive presence back from the room: joined.
458        xml = u"""
459            <presence to='%s' from='%s'/>
460        """ % (self.userJID, self.occupantJID)
461        self.stub.send(parseXml(xml))
462
463        # The presence for leaving the room should have been sent now.
464        element = self.stub.output[-1]
465
466        self.assertEquals('unavailable', element['type'],
467                          'Unavailable is not being sent')
468
469        # Receive presence back from the room: left.
470        xml = u"""
471            <presence to='%s' from='%s' type='unavailable'/>
472        """ % (self.userJID, self.occupantJID)
473        self.stub.send(parseXml(xml))
474
475        return d
476
477
478    def test_groupChat(self):
479        """
480        Send private messages to muc entities.
481        """
482        self.protocol.groupChat(self.roomJID, u'This is a test')
483
484        message = self.stub.output[-1]
485
486        self.assertEquals('message', message.name)
487        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
488        self.assertEquals('groupchat', message.getAttribute('type'))
489        self.assertEquals(u'This is a test', unicode(message.body))
490
491
492    def test_chat(self):
493        """
494        Send private messages to muc entities.
495        """
496        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
497
498        self.protocol.chat(otherOccupantJID, u'This is a test')
499
500        message = self.stub.output[-1]
501
502        self.assertEquals('message', message.name)
503        self.assertEquals(otherOccupantJID.full(), message.getAttribute('to'))
504        self.assertEquals('chat', message.getAttribute('type'))
505        self.assertEquals(u'This is a test', unicode(message.body))
506
507
508    def test_subject(self):
509        """
510        Change subject of the room.
511        """
512        self.protocol.subject(self.roomJID, u'This is a test')
513
514        message = self.stub.output[-1]
515
516        self.assertEquals('message', message.name)
517        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
518        self.assertEquals('groupchat', message.getAttribute('type'))
519        self.assertEquals(u'This is a test', unicode(message.subject))
520
521
522    def test_invite(self):
523        """
524        Invite a user to a room
525        """
526        invitee = JID('other@example.org')
527
528        self.protocol.invite(self.roomJID, invitee, u'This is a test')
529
530        message = self.stub.output[-1]
531
532        self.assertEquals('message', message.name)
533        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
534        self.assertEquals(muc.NS_MUC_USER, message.x.uri)
535        self.assertEquals(muc.NS_MUC_USER, message.x.invite.uri)
536        self.assertEquals(invitee.full(), message.x.invite.getAttribute('to'))
537        self.assertEquals(muc.NS_MUC_USER, message.x.invite.reason.uri)
538        self.assertEquals(u'This is a test', unicode(message.x.invite.reason))
539
540
541    def test_getRegisterForm(self):
542        """
543        The response of a register form request should extract the form.
544        """
545
546        def cb(form):
547            self.assertEquals('form', form.formType)
548
549        d = self.protocol.getRegisterForm(self.roomJID)
550        d.addCallback(cb)
551
552        iq = self.stub.output[-1]
553
554        query = "/iq/query[@xmlns='%s']" % (muc.NS_REGISTER)
555        nodes = xpath.queryForNodes(query, iq)
556        self.assertNotIdentical(None, nodes, 'Missing query element')
557
558        self.assertRaises(StopIteration, nodes[0].elements().next)
559
560        xml = u"""
561            <iq from='%s' id='%s' to='%s' type='result'>
562              <query xmlns='jabber:iq:register'>
563                <x xmlns='jabber:x:data' type='form'>
564                  <field type='hidden'
565                         var='FORM_TYPE'>
566                    <value>http://jabber.org/protocol/muc#register</value>
567                  </field>
568                  <field label='Desired Nickname'
569                         type='text-single'
570                         var='muc#register_roomnick'>
571                    <required/>
572                  </field>
573                </x>
574              </query>
575            </iq>
576        """ % (self.roomJID, iq['id'], self.userJID)
577        self.stub.send(parseXml(xml))
578
579        return d
580
581
582    def test_register(self):
583        """
584        Client registering with a room.
585
586        http://xmpp.org/extensions/xep-0045.html#register
587        """
588
589        def cb(iq):
590            # check for a result
591            self.assertEquals('result', iq['type'], 'We did not get a result')
592
593        d = self.protocol.register(self.roomJID,
594                                   {'muc#register_roomnick': 'thirdwitch'})
595        d.addCallback(cb)
596
597        iq = self.stub.output[-1]
598
599        query = "/iq/query[@xmlns='%s']" % muc.NS_REGISTER
600        nodes = xpath.queryForNodes(query, iq)
601        self.assertNotIdentical(None, nodes, 'Invalid registration request')
602
603        form = data_form.findForm(nodes[0], muc.NS_MUC_REGISTER)
604        self.assertNotIdentical(None, form, 'Missing registration form')
605        self.assertEquals('submit', form.formType)
606        self.assertIn('muc#register_roomnick', form.fields)
607
608
609        response = toResponse(iq, 'result')
610        self.stub.send(response)
611        return d
612
613
614    def test_voice(self):
615        """
616        Client requesting voice for a room.
617        """
618        self.protocol.voice(self.occupantJID)
619
620        m = self.stub.output[-1]
621
622        query = ("/message/x[@type='submit']/field/value"
623                    "[text()='%s']") % muc.NS_MUC_REQUEST
624        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
625
626
627    def test_history(self):
628        """
629        Converting a one to one chat to a multi-user chat.
630        """
631        archive = []
632        thread = "e0ffe42b28561960c6b12b944a092794b9683a38"
633        # create messages
634        element = domish.Element((None, 'message'))
635        element['to'] = 'testing@example.com'
636        element['type'] = 'chat'
637        element.addElement('body', None, 'test')
638        element.addElement('thread', None, thread)
639
640        archive.append({'stanza': element,
641                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
642                                              tzinfo=tzutc())})
643
644        element = domish.Element((None, 'message'))
645        element['to'] = 'testing2@example.com'
646        element['type'] = 'chat'
647        element.addElement('body', None, 'yo')
648        element.addElement('thread', None, thread)
649
650        archive.append({'stanza': element,
651                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
652                                              tzinfo=tzutc())})
653
654        self.protocol.history(self.occupantJID, archive)
655
656
657        while len(self.stub.output)>0:
658            element = self.stub.output.pop()
659            # check for delay element
660            self.assertEquals('message', element.name, 'Wrong stanza')
661            self.assertTrue(xpath.matches("/message/delay", element),
662                            'Invalid history stanza')
663
664
665    def test_getConfiguration(self):
666        """
667        The response of a configure form request should extract the form.
668        """
669
670        def cb(form):
671            self.assertEquals('form', form.formType)
672
673        d = self.protocol.getConfiguration(self.roomJID)
674        d.addCallback(cb)
675
676        iq = self.stub.output[-1]
677
678        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
679        nodes = xpath.queryForNodes(query, iq)
680        self.assertNotIdentical(None, nodes, 'Missing query element')
681
682        self.assertRaises(StopIteration, nodes[0].elements().next)
683
684        xml = u"""
685            <iq from='%s' id='%s' to='%s' type='result'>
686              <query xmlns='http://jabber.org/protocol/muc#owner'>
687                <x xmlns='jabber:x:data' type='form'>
688                  <field type='hidden'
689                         var='FORM_TYPE'>
690                    <value>http://jabber.org/protocol/muc#roomconfig</value>
691                  </field>
692                  <field label='Natural-Language Room Name'
693                         type='text-single'
694                         var='muc#roomconfig_roomname'/>
695                </x>
696              </query>
697            </iq>
698        """ % (self.roomJID, iq['id'], self.userJID)
699        self.stub.send(parseXml(xml))
700
701        return d
702
703
704    def test_getConfigurationNoOptions(self):
705        """
706        The response of a configure form request should extract the form.
707        """
708
709        def cb(form):
710            self.assertIdentical(None, form)
711
712        d = self.protocol.getConfiguration(self.roomJID)
713        d.addCallback(cb)
714
715        iq = self.stub.output[-1]
716
717        xml = u"""
718            <iq from='%s' id='%s' to='%s' type='result'>
719              <query xmlns='http://jabber.org/protocol/muc#owner'/>
720            </iq>
721        """ % (self.roomJID, iq['id'], self.userJID)
722        self.stub.send(parseXml(xml))
723
724        return d
725
726
727    def test_configure(self):
728        """
729        Default configure and changing the room name.
730        """
731
732        def cb(iq):
733            self.assertEquals('result', iq['type'], 'Not a result')
734
735        values = {'muc#roomconfig_roomname': self.roomIdentifier}
736
737        d = self.protocol.configure(self.roomJID, values)
738        d.addCallback(cb)
739
740        iq = self.stub.output[-1]
741
742        self.assertEquals('set', iq.getAttribute('type'))
743        self.assertEquals(self.roomJID.full(), iq.getAttribute('to'))
744
745        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
746        nodes = xpath.queryForNodes(query, iq)
747        self.assertNotIdentical(None, nodes, 'Bad configure request')
748
749        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
750        self.assertNotIdentical(None, form, 'Missing configuration form')
751        self.assertEquals('submit', form.formType)
752
753        response = toResponse(iq, 'result')
754        self.stub.send(response)
755        return d
756
757
758    def test_configureCancel(self):
759        """
760        Cancelling room configuration should send a cancel form.
761        """
762
763        d = self.protocol.configure(self.roomJID, None)
764
765        iq = self.stub.output[-1]
766
767        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
768        nodes = xpath.queryForNodes(query, iq)
769
770        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
771        self.assertNotIdentical(None, form, 'Missing configuration form')
772        self.assertEquals('cancel', form.formType)
773
774        response = toResponse(iq, 'result')
775        self.stub.send(response)
776        return d
777
778
779    def test_getMemberList(self):
780        """
781        Retrieving the member list returns a list of L{muc.AdminItem}s
782
783        The request asks for the affiliation C{'member'}.
784        """
785        def cb(items):
786            self.assertEquals(1, len(items))
787            item = items[0]
788            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
789            self.assertEquals(u'thirdwitch', item.nick)
790            self.assertEquals(u'member', item.affiliation)
791
792        d = self.protocol.getMemberList(self.roomJID)
793        d.addCallback(cb)
794
795        iq = self.stub.output[-1]
796        self.assertEquals('get', iq.getAttribute('type'))
797        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
798                                                              muc.NS_MUC_ADMIN)
799        items = xpath.queryForNodes(query, iq)
800        self.assertNotIdentical(None, items)
801        self.assertEquals(1, len(items))
802        self.assertEquals('member', items[0].getAttribute('affiliation'))
803
804        response = toResponse(iq, 'result')
805        query = response.addElement((NS_MUC_ADMIN, 'query'))
806        item = query.addElement('item')
807        item['affiliation'] ='member'
808        item['jid'] = 'hag66@shakespeare.lit'
809        item['nick'] = 'thirdwitch'
810        item['role'] = 'participant'
811        self.stub.send(response)
812
813        return d
814
815
816    def test_getAdminList(self):
817        """
818        Retrieving the admin list returns a list of L{muc.AdminItem}s
819
820        The request asks for the affiliation C{'admin'}.
821        """
822        d = self.protocol.getAdminList(self.roomJID)
823
824        iq = self.stub.output[-1]
825        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
826                                                              muc.NS_MUC_ADMIN)
827        items = xpath.queryForNodes(query, iq)
828        self.assertEquals('admin', items[0].getAttribute('affiliation'))
829
830        response = toResponse(iq, 'result')
831        query = response.addElement((NS_MUC_ADMIN, 'query'))
832        self.stub.send(response)
833
834        return d
835
836
837    def test_getBanList(self):
838        """
839        Retrieving the ban list returns a list of L{muc.AdminItem}s
840
841        The request asks for the affiliation C{'outcast'}.
842        """
843        def cb(items):
844            self.assertEquals(1, len(items))
845            item = items[0]
846            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
847            self.assertEquals(u'outcast', item.affiliation)
848            self.assertEquals(u'Trouble making', item.reason)
849
850        d = self.protocol.getBanList(self.roomJID)
851        d.addCallback(cb)
852
853        iq = self.stub.output[-1]
854        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
855                                                              muc.NS_MUC_ADMIN)
856        items = xpath.queryForNodes(query, iq)
857        self.assertEquals('outcast', items[0].getAttribute('affiliation'))
858
859        response = toResponse(iq, 'result')
860        query = response.addElement((NS_MUC_ADMIN, 'query'))
861        item = query.addElement('item')
862        item['affiliation'] ='outcast'
863        item['jid'] = 'hag66@shakespeare.lit'
864        item.addElement('reason', content='Trouble making')
865        self.stub.send(response)
866
867        return d
868
869
870    def test_getOwnerList(self):
871        """
872        Retrieving the owner list returns a list of L{muc.AdminItem}s
873
874        The request asks for the affiliation C{'owner'}.
875        """
876        d = self.protocol.getOwnerList(self.roomJID)
877
878        iq = self.stub.output[-1]
879        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
880                                                              muc.NS_MUC_ADMIN)
881        items = xpath.queryForNodes(query, iq)
882        self.assertEquals('owner', items[0].getAttribute('affiliation'))
883
884        response = toResponse(iq, 'result')
885        query = response.addElement((NS_MUC_ADMIN, 'query'))
886        self.stub.send(response)
887
888        return d
889
890
891    def test_getModeratorList(self):
892        """
893        Retrieving the moderator returns a list of L{muc.AdminItem}s.
894
895        The request asks for the role C{'moderator'}.
896        """
897
898        def cb(items):
899            self.assertEquals(1, len(items))
900            item = items[0]
901            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
902            self.assertEquals(u'thirdwitch', item.nick)
903            self.assertEquals(u'moderator', item.role)
904
905        d = self.protocol.getModeratorList(self.roomJID)
906        d.addCallback(cb)
907
908        iq = self.stub.output[-1]
909        self.assertEquals('get', iq.getAttribute('type'))
910        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
911                                                              muc.NS_MUC_ADMIN)
912        items = xpath.queryForNodes(query, iq)
913        self.assertNotIdentical(None, items)
914        self.assertEquals(1, len(items))
915        self.assertEquals('moderator', items[0].getAttribute('role'))
916
917        response = toResponse(iq, 'result')
918        query = response.addElement((NS_MUC_ADMIN, 'query'))
919        item = query.addElement('item')
920        item['affiliation'] ='member'
921        item['jid'] = 'hag66@shakespeare.lit'
922        item['nick'] = 'thirdwitch'
923        item['role'] = 'moderator'
924        self.stub.send(response)
925
926        return d
927
928
929    def test_modifyAffiliationList(self):
930
931        entities = [JID('user1@test.example.org'),
932                    JID('user2@test.example.org')]
933        d = self.protocol.modifyAffiliationList(self.roomJID, entities,
934                                                'admin')
935
936        iq = self.stub.output[-1]
937        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
938                                                              muc.NS_MUC_ADMIN)
939        items = xpath.queryForNodes(query, iq)
940        self.assertNotIdentical(None, items)
941        self.assertEquals(entities[0], JID(items[0].getAttribute('jid')))
942        self.assertEquals('admin', items[0].getAttribute('affiliation'))
943        self.assertEquals(entities[1], JID(items[1].getAttribute('jid')))
944        self.assertEquals('admin', items[1].getAttribute('affiliation'))
945
946        # Send a response to have the deferred fire.
947        response = toResponse(iq, 'result')
948        self.stub.send(response)
949        return d
950
951
952    def test_grantVoice(self):
953        """
954        Granting voice sends request to set role to 'participant'.
955        """
956        nick = 'TroubleMaker'
957        def cb(give_voice):
958            self.assertTrue(give_voice, 'Did not give voice user')
959
960        d = self.protocol.grantVoice(self.roomJID, nick,
961                                     sender=self.userJID)
962        d.addCallback(cb)
963
964        iq = self.stub.output[-1]
965
966        query = (u"/iq[@type='set' and @to='%s']/query/item"
967                     "[@role='participant']") % self.roomJID
968        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
969
970        response = toResponse(iq, 'result')
971        self.stub.send(response)
972        return d
973
974
975    def test_revokeVoice(self):
976        """
977        Revoking voice sends request to set role to 'visitor'.
978        """
979        nick = 'TroubleMaker'
980
981        d = self.protocol.revokeVoice(self.roomJID, nick,
982                                      reason="Trouble maker",
983                                      sender=self.userJID)
984
985        iq = self.stub.output[-1]
986
987        query = (u"/iq[@type='set' and @to='%s']/query/item"
988                     "[@role='visitor']") % self.roomJID
989        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
990
991        response = toResponse(iq, 'result')
992        self.stub.send(response)
993        return d
994
995
996    def test_grantModerator(self):
997        """
998        Granting moderator privileges sends request to set role to 'moderator'.
999        """
1000        nick = 'TroubleMaker'
1001
1002        d = self.protocol.grantModerator(self.roomJID, nick,
1003                                         sender=self.userJID)
1004
1005        iq = self.stub.output[-1]
1006
1007        query = (u"/iq[@type='set' and @to='%s']/query/item"
1008                     "[@role='moderator']") % self.roomJID
1009        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
1010
1011        response = toResponse(iq, 'result')
1012        self.stub.send(response)
1013        return d
1014
1015
1016    def test_ban(self):
1017        """
1018        Ban an entity in a room.
1019        """
1020        banned = JID('ban@jabber.org/TroubleMaker')
1021
1022        def cb(banned):
1023            self.assertTrue(banned, 'Did not ban user')
1024
1025        d = self.protocol.ban(self.roomJID, banned, reason='Spam',
1026                              sender=self.userJID)
1027        d.addCallback(cb)
1028
1029        iq = self.stub.output[-1]
1030
1031        self.assertTrue(xpath.matches(
1032                u"/iq[@type='set' and @to='%s']/query/item"
1033                    "[@affiliation='outcast']" % (self.roomJID,),
1034                iq),
1035            'Wrong ban stanza')
1036
1037        response = toResponse(iq, 'result')
1038        self.stub.send(response)
1039
1040        return d
1041
1042
1043    def test_kick(self):
1044        """
1045        Kick an entity from a room.
1046        """
1047        nick = 'TroubleMaker'
1048
1049        def cb(kicked):
1050            self.assertTrue(kicked, 'Did not kick user')
1051
1052        d = self.protocol.kick(self.roomJID, nick, reason='Spam',
1053                               sender=self.userJID)
1054        d.addCallback(cb)
1055
1056        iq = self.stub.output[-1]
1057
1058        self.assertTrue(xpath.matches(
1059                u"/iq[@type='set' and @to='%s']/query/item"
1060                    "[@role='none']" % (self.roomJID,),
1061                iq),
1062            'Wrong kick stanza')
1063
1064        response = toResponse(iq, 'result')
1065        self.stub.send(response)
1066
1067        return d
1068
1069
1070    def test_destroy(self):
1071        """
1072        Destroy a room.
1073        """
1074        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
1075                                  alternate=JID('other@%s' % self.service),
1076                                  password='secret')
1077
1078        iq = self.stub.output[-1]
1079
1080        query = ("/iq/query[@xmlns='%s']/destroy[@xmlns='%s']" %
1081                 (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER))
1082
1083        nodes = xpath.queryForNodes(query, iq)
1084        self.assertNotIdentical(None, nodes, 'Bad configure request')
1085        destroy = nodes[0]
1086        self.assertEquals('Time to leave', unicode(destroy.reason))
1087
1088        response = toResponse(iq, 'result')
1089        self.stub.send(response)
1090        return d
1091
1092
1093
1094class MUCClientTest(unittest.TestCase):
1095    """
1096    Tests for C{muc.MUCClient}.
1097    """
1098
1099    def setUp(self):
1100        self.clock = task.Clock()
1101        self.sessionManager = TestableStreamManager(reactor=self.clock)
1102        self.stub = self.sessionManager.stub
1103        self.protocol = muc.MUCClient(reactor=self.clock)
1104        self.protocol.setHandlerParent(self.sessionManager)
1105
1106        self.roomIdentifier = 'test'
1107        self.service  = 'conference.example.org'
1108        self.nick = 'Nick'
1109
1110        self.occupantJID = JID(tuple=(self.roomIdentifier,
1111                                      self.service,
1112                                      self.nick))
1113        self.roomJID = self.occupantJID.userhostJID()
1114        self.userJID = JID('test@example.org/Testing')
1115
1116
1117    def _createRoom(self):
1118        """
1119        A helper method to create a test room.
1120        """
1121        # create a room
1122        room = muc.Room(self.roomIdentifier,
1123                        self.service,
1124                        self.nick)
1125        self.protocol._addRoom(room)
1126
1127
1128    def test_interface(self):
1129        """
1130        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
1131        """
1132        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
1133
1134
1135    def test_userJoinedRoom(self):
1136        """
1137        Joins by others to a room we're in are passed to userJoinedRoom
1138        """
1139        xml = """
1140            <presence to='%s' from='%s'>
1141              <x xmlns='http://jabber.org/protocol/muc#user'>
1142                <item affiliation='member' role='participant'/>
1143              </x>
1144            </presence>
1145        """ % (self.userJID.full(), self.occupantJID.full())
1146
1147        # create a room
1148        self._createRoom()
1149
1150        def userJoinedRoom(room, user):
1151            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
1152                              'Wrong room name')
1153            self.assertTrue(room.inRoster(user), 'User not in roster')
1154
1155        d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom)
1156        self.stub.send(parseXml(xml))
1157        return d
1158
1159
1160    def test_receivedSubject(self):
1161        """
1162        Subject received from a room we're in are passed to receivedSubject.
1163        """
1164        xml = u"""
1165            <message to='%s' from='%s' type='groupchat'>
1166              <subject>test</subject>
1167            </message>
1168        """ % (self.userJID, self.occupantJID)
1169
1170        self._createRoom()
1171
1172        # add user to room
1173        user = muc.User(self.nick)
1174        room = self.protocol._getRoom(self.roomJID)
1175        room.addUser(user)
1176
1177        def receivedSubject(room, user, subject):
1178            self.assertEquals('test', subject, "Wrong group chat message")
1179            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
1180                              'Wrong room name')
1181            self.assertEquals(self.nick, user.nick)
1182
1183        d, self.protocol.receivedSubject = calledAsync(receivedSubject)
1184        self.stub.send(parseXml(xml))
1185        return d
1186
1187
1188    def test_receivedGroupChat(self):
1189        """
1190        Messages received from a room we're in are passed to receivedGroupChat.
1191        """
1192        xml = u"""
1193            <message to='test@test.com' from='%s' type='groupchat'>
1194              <body>test</body>
1195            </message>
1196        """ % (self.occupantJID)
1197
1198        self._createRoom()
1199
1200        def receivedGroupChat(room, user, message):
1201            self.assertEquals('test', message.body, "Wrong group chat message")
1202            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
1203                              'Wrong room name')
1204
1205        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1206        self.stub.send(parseXml(xml))
1207        return d
1208
1209
1210    def test_receivedGroupChatRoom(self):
1211        """
1212        Messages received from the room itself have C{user} set to C{None}.
1213        """
1214        xml = u"""
1215            <message to='test@test.com' from='%s' type='groupchat'>
1216              <body>test</body>
1217            </message>
1218        """ % (self.roomJID)
1219
1220        self._createRoom()
1221
1222        def receivedGroupChat(room, user, message):
1223            self.assertIdentical(None, user)
1224
1225        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
1226        self.stub.send(parseXml(xml))
1227        return d
1228
1229
1230    def test_join(self):
1231        """
1232        Joining a room waits for confirmation, deferred fires room.
1233        """
1234
1235        def cb(room):
1236            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
1237
1238        d = self.protocol.join(self.roomJID, self.nick)
1239        d.addCallback(cb)
1240
1241        # send back user presence, they joined
1242        xml = """
1243            <presence from='%s@%s/%s'>
1244              <x xmlns='http://jabber.org/protocol/muc#user'>
1245                <item affiliation='member' role='participant'/>
1246              </x>
1247            </presence>
1248        """ % (self.roomIdentifier, self.service, self.nick)
1249        self.stub.send(parseXml(xml))
1250        return d
1251
1252
1253    def test_userLeftRoom(self):
1254        """
1255        Unavailable presence from a participant removes it from the room.
1256        """
1257
1258        xml = u"""
1259            <presence to='%s' from='%s' type='unavailable'/>
1260        """ % (self.userJID, self.occupantJID)
1261
1262        # create a room
1263        self._createRoom()
1264
1265        # add user to room
1266        user = muc.User(self.nick)
1267        room = self.protocol._getRoom(self.roomJID)
1268        room.addUser(user)
1269
1270        def userLeftRoom(room, user):
1271            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
1272                              'Wrong room name')
1273            self.assertFalse(room.inRoster(user), 'User in roster')
1274
1275        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
1276        self.stub.send(parseXml(xml))
1277        return d
1278
1279
1280    def test_receivedHistory(self):
1281        """
1282        Receiving history on room join.
1283        """
1284        xml = u"""
1285            <message to='test@test.com' from='%s' type='groupchat'>
1286              <body>test</body>
1287              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
1288                                            from="%s"/>
1289            </message>
1290        """ % (self.occupantJID, self.userJID)
1291
1292        self._createRoom()
1293
1294
1295        def receivedHistory(room, user, message):
1296            self.assertEquals('test', message.body, "wrong message body")
1297            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
1298            self.assertEquals(stamp, message.delay.stamp,
1299                             'Does not have a history stamp')
1300
1301        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
1302        self.stub.send(parseXml(xml))
1303        return d
1304
1305
1306    def test_nick(self):
1307        """
1308        Send a nick change to the server.
1309        """
1310        newNick = 'newNick'
1311
1312        self._createRoom()
1313
1314        def cb(room):
1315            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
1316            self.assertEquals(newNick, room.nick)
1317
1318        d = self.protocol.nick(self.roomJID, newNick)
1319        d.addCallback(cb)
1320
1321        # send back user presence, nick changed
1322        xml = u"""
1323            <presence from='%s/%s'>
1324              <x xmlns='http://jabber.org/protocol/muc#user'>
1325                <item affiliation='member' role='participant'/>
1326              </x>
1327            </presence>
1328        """ % (self.roomJID, newNick)
1329        self.stub.send(parseXml(xml))
1330        return d
1331
1332
1333    def test_status(self):
1334        """
1335        Change status
1336        """
1337        def joined(_):
1338            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
1339            d.addCallback(statusChanged)
1340            return d
1341
1342        def statusChanged(room):
1343            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
1344            user = room.getUser(self.nick)
1345            self.assertNotIdentical(None, user, 'User not found')
1346            self.assertEquals('testing MUC', user.status, 'Wrong status')
1347            self.assertEquals('xa', user.show, 'Wrong show')
1348
1349        # Join the room
1350        d = self.protocol.join(self.roomJID, self.nick)
1351        d.addCallback(joined)
1352
1353        # Receive presence back from the room: joined.
1354        xml = u"""
1355            <presence to='%s' from='%s'/>
1356        """ % (self.userJID, self.occupantJID)
1357        self.stub.send(parseXml(xml))
1358
1359        # send back user presence, status changed
1360        xml = u"""
1361            <presence from='%s'>
1362              <x xmlns='http://jabber.org/protocol/muc#user'>
1363                <item affiliation='member' role='participant'/>
1364              </x>
1365              <show>xa</show>
1366              <status>testing MUC</status>
1367            </presence>
1368        """ % self.occupantJID
1369        self.stub.send(parseXml(xml))
1370
1371        return d
Note: See TracBrowser for help on using the repository browser.