source: wokkel/test/test_muc.py @ 145:216c953d8ecd

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

Add more tests, remove code redundancy and cleanup variable names.

This adds tests for timeouts in response to joins, nick and presence changes,
error responses from the room JID instead of the occupant JID and receiving
room subject changes.

File size: 23.1 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, iwokkel, muc
21from wokkel.generic import parseXml
22from wokkel.test.helpers import XmlStreamStub
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 MUCClientTest(unittest.TestCase):
46    timeout = 2
47
48    def setUp(self):
49        self.stub = XmlStreamStub()
50        self.clock = task.Clock()
51        self.protocol = muc.MUCClient(reactor=self.clock)
52        self.protocol.xmlstream = self.stub.xmlstream
53        self.protocol.connectionInitialized()
54        self.roomIdentifier = 'test'
55        self.service  = 'conference.example.org'
56        self.nick = 'Nick'
57
58        self.occupantJID = JID(tuple=(self.roomIdentifier,
59                                      self.service,
60                                      self.nick))
61        self.roomJID = self.occupantJID.userhostJID()
62        self.userJID = JID('test@example.org/Testing')
63
64
65    def _createRoom(self):
66        """
67        A helper method to create a test room.
68        """
69        # create a room
70        room = muc.Room(self.roomIdentifier,
71                        self.service,
72                        self.nick)
73        self.protocol._addRoom(room)
74
75
76    def test_interface(self):
77        """
78        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
79        """
80        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
81
82
83    def test_userJoinedRoom(self):
84        """
85        Joins by others to a room we're in are passed to userJoinedRoom
86        """
87        xml = """
88            <presence to='%s' from='%s'>
89              <x xmlns='http://jabber.org/protocol/muc#user'>
90                <item affiliation='member' role='participant'/>
91              </x>
92            </presence>
93        """ % (self.userJID.full(), self.occupantJID.full())
94
95        # create a room
96        self._createRoom()
97
98        def userJoinedRoom(room, user):
99            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
100                              'Wrong room name')
101            self.assertTrue(room.inRoster(user), 'User not in roster')
102
103        d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom)
104        self.stub.send(parseXml(xml))
105        return d
106
107
108    def test_receivedSubject(self):
109        """
110        Subject received from a room we're in are passed to receivedSubject.
111        """
112        xml = u"""
113            <message to='%s' from='%s' type='groupchat'>
114              <subject>test</subject>
115            </message>
116        """ % (self.userJID, self.occupantJID)
117
118        self._createRoom()
119
120        # add user to room
121        user = muc.User(self.nick)
122        room = self.protocol._getRoom(self.roomJID)
123        room.addUser(user)
124
125        def receivedSubject(room, user, subject):
126            self.assertEquals('test', subject, "Wrong group chat message")
127            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
128                              'Wrong room name')
129            self.assertEquals(self.nick, user.nick)
130
131        d, self.protocol.receivedSubject = calledAsync(receivedSubject)
132        self.stub.send(parseXml(xml))
133        return d
134
135
136    def test_receivedGroupChat(self):
137        """
138        Messages received from a room we're in are passed to receivedGroupChat.
139        """
140        xml = u"""
141            <message to='test@test.com' from='%s' type='groupchat'>
142              <body>test</body>
143            </message>
144        """ % (self.occupantJID)
145
146        self._createRoom()
147
148        def receivedGroupChat(room, user, message):
149            self.assertEquals('test', message.body, "Wrong group chat message")
150            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
151                              'Wrong room name')
152
153        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
154        self.stub.send(parseXml(xml))
155        return d
156
157
158    def test_receivedGroupChatRoom(self):
159        """
160        Messages received from the room itself have C{user} set to C{None}.
161        """
162        xml = u"""
163            <message to='test@test.com' from='%s' type='groupchat'>
164              <body>test</body>
165            </message>
166        """ % (self.roomJID)
167
168        self._createRoom()
169
170        def receivedGroupChat(room, user, message):
171            self.assertIdentical(None, user)
172
173        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
174        self.stub.send(parseXml(xml))
175        return d
176
177
178    def test_join(self):
179        """
180        Joining a room waits for confirmation, deferred fires room.
181        """
182
183        def cb(room):
184            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
185
186        d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
187        d.addCallback(cb)
188
189        element = self.stub.output[-1]
190        self.assertEquals('presence', element.name, "Need to be presence")
191        self.assertNotIdentical(None, element.x, 'No muc x element')
192
193        # send back user presence, they joined
194        xml = """
195            <presence from='%s@%s/%s'>
196              <x xmlns='http://jabber.org/protocol/muc#user'>
197                <item affiliation='member' role='participant'/>
198              </x>
199            </presence>
200        """ % (self.roomIdentifier, self.service, self.nick)
201        self.stub.send(parseXml(xml))
202        return d
203
204
205    def test_joinForbidden(self):
206        """
207        A forbidden error in response to a join errbacks with L{StanzaError}.
208        """
209
210        def cb(error):
211            self.assertEquals('forbidden', error.condition,
212                              'Wrong muc condition')
213
214        d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
215        self.assertFailure(d, StanzaError)
216        d.addCallback(cb)
217
218        # send back error, forbidden
219        xml = u"""
220            <presence from='%s' type='error'>
221              <error type='auth'>
222                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
223              </error>
224            </presence>
225        """ % (self.occupantJID)
226        self.stub.send(parseXml(xml))
227        return d
228
229
230    def test_joinForbiddenFromRoomJID(self):
231        """
232        An error response to a join sent from the room JID should errback.
233
234        Some service implementations send error stanzas from the room JID
235        instead of the JID the join presence was sent to.
236        """
237
238        d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
239        self.assertFailure(d, StanzaError)
240
241        # send back error, forbidden
242        xml = u"""
243            <presence from='%s' type='error'>
244              <error type='auth'>
245                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
246              </error>
247            </presence>
248        """ % (self.roomJID)
249        self.stub.send(parseXml(xml))
250        return d
251
252
253    def test_joinBadJID(self):
254        """
255        Client joining a room and getting a jid-malformed error.
256        """
257
258        def cb(error):
259            self.assertEquals('jid-malformed', error.condition,
260                              'Wrong muc condition')
261
262        d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
263        self.assertFailure(d, StanzaError)
264        d.addCallback(cb)
265
266        # send back error, bad JID
267        xml = u"""
268            <presence from='%s' type='error'>
269              <error type='modify'>
270                <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
271              </error>
272            </presence>
273        """ % (self.occupantJID)
274        self.stub.send(parseXml(xml))
275        return d
276
277
278    def test_joinTimeout(self):
279        """
280        After not receiving a response to a join, errback with L{TimeoutError}.
281        """
282
283        d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
284        self.assertFailure(d, TimeoutError)
285        self.clock.advance(muc.DEFER_TIMEOUT)
286        return d
287
288
289    def test_leave(self):
290        """
291        Client leaves a room
292        """
293        def cb(left):
294            self.assertTrue(left, 'did not leave room')
295
296        self._createRoom()
297        d = self.protocol.leave(self.roomJID)
298        d.addCallback(cb)
299
300        element = self.stub.output[-1]
301
302        self.assertEquals('unavailable', element['type'],
303                          'Unavailable is not being sent')
304
305        xml = u"""
306            <presence to='%s' from='%s' type='unavailable'/>
307        """ % (self.userJID, self.occupantJID)
308        self.stub.send(parseXml(xml))
309        return d
310
311
312    def test_userLeftRoom(self):
313        """
314        Unavailable presence from a participant removes it from the room.
315        """
316
317        xml = u"""
318            <presence to='%s' from='%s' type='unavailable'/>
319        """ % (self.userJID, self.occupantJID)
320
321        # create a room
322        self._createRoom()
323
324        # add user to room
325        user = muc.User(self.nick)
326        room = self.protocol._getRoom(self.roomJID)
327        room.addUser(user)
328
329        def userLeftRoom(room, user):
330            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
331                              'Wrong room name')
332            self.assertFalse(room.inRoster(user), 'User in roster')
333
334        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
335        self.stub.send(parseXml(xml))
336        return d
337
338
339    def test_ban(self):
340        """
341        Ban an entity in a room.
342        """
343        banned = JID('ban@jabber.org/TroubleMaker')
344
345        def cb(banned):
346            self.assertTrue(banned, 'Did not ban user')
347
348        d = self.protocol.ban(self.occupantJID, banned, reason='Spam',
349                              sender=self.userJID)
350        d.addCallback(cb)
351
352        iq = self.stub.output[-1]
353
354        self.assertTrue(xpath.matches(
355                u"/iq[@type='set' and @to='%s']/query/item"
356                    "[@affiliation='outcast']" % (self.roomJID,),
357                iq),
358            'Wrong ban stanza')
359
360        response = toResponse(iq, 'result')
361        self.stub.send(response)
362
363        return d
364
365
366    def test_kick(self):
367        """
368        Kick an entity from a room.
369        """
370        nick = 'TroubleMaker'
371
372        def cb(kicked):
373            self.assertTrue(kicked, 'Did not kick user')
374
375        d = self.protocol.kick(self.occupantJID, nick, reason='Spam',
376                               sender=self.userJID)
377        d.addCallback(cb)
378
379        iq = self.stub.output[-1]
380
381        self.assertTrue(xpath.matches(
382                u"/iq[@type='set' and @to='%s']/query/item"
383                    "[@affiliation='none']" % (self.roomJID,),
384                iq),
385            'Wrong kick stanza')
386
387        response = toResponse(iq, 'result')
388        self.stub.send(response)
389
390        return d
391
392
393    def test_password(self):
394        """
395        Sending a password via presence to a password protected room.
396        """
397
398        self.protocol.password(self.occupantJID, 'secret')
399
400        element = self.stub.output[-1]
401
402        self.assertTrue(xpath.matches(
403                u"/presence[@to='%s']/x/password"
404                    "[text()='secret']" % (self.occupantJID,),
405                element),
406            'Wrong presence stanza')
407
408
409    def test_receivedHistory(self):
410        """
411        Receiving history on room join.
412        """
413        xml = u"""
414            <message to='test@test.com' from='%s' type='groupchat'>
415              <body>test</body>
416              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
417                                            from="%s"/>
418            </message>
419        """ % (self.occupantJID, self.userJID)
420
421        self._createRoom()
422
423
424        def receivedHistory(room, user, message):
425            self.assertEquals('test', message.body, "wrong message body")
426            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
427            self.assertEquals(stamp, message.delay.stamp,
428                             'Does not have a history stamp')
429
430        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
431        self.stub.send(parseXml(xml))
432        return d
433
434
435    def test_oneToOneChat(self):
436        """
437        Converting a one to one chat to a multi-user chat.
438        """
439        archive = []
440        thread = "e0ffe42b28561960c6b12b944a092794b9683a38"
441        # create messages
442        element = domish.Element((None, 'message'))
443        element['to'] = 'testing@example.com'
444        element['type'] = 'chat'
445        element.addElement('body', None, 'test')
446        element.addElement('thread', None, thread)
447
448        archive.append({'stanza': element,
449                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
450                                              tzinfo=tzutc())})
451
452        element = domish.Element((None, 'message'))
453        element['to'] = 'testing2@example.com'
454        element['type'] = 'chat'
455        element.addElement('body', None, 'yo')
456        element.addElement('thread', None, thread)
457
458        archive.append({'stanza': element,
459                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
460                                              tzinfo=tzutc())})
461
462        self.protocol.history(self.occupantJID, archive)
463
464
465        while len(self.stub.output)>0:
466            element = self.stub.output.pop()
467            # check for delay element
468            self.assertEquals('message', element.name, 'Wrong stanza')
469            self.assertTrue(xpath.matches("/message/delay", element),
470                            'Invalid history stanza')
471
472
473    def test_invite(self):
474        """
475        Invite a user to a room
476        """
477        invitee = JID('other@example.org')
478
479        self.protocol.invite(self.roomJID, invitee, u'This is a test')
480
481        message = self.stub.output[-1]
482
483        self.assertEquals('message', message.name)
484        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
485        self.assertEquals(muc.NS_MUC_USER, message.x.uri)
486        self.assertEquals(muc.NS_MUC_USER, message.x.invite.uri)
487        self.assertEquals(invitee.full(), message.x.invite.getAttribute('to'))
488        self.assertEquals(muc.NS_MUC_USER, message.x.invite.reason.uri)
489        self.assertEquals(u'This is a test', unicode(message.x.invite.reason))
490
491
492    def test_groupChat(self):
493        """
494        Send private messages to muc entities.
495        """
496        self.protocol.groupChat(self.roomJID, u'This is a test')
497
498        message = self.stub.output[-1]
499
500        self.assertEquals('message', message.name)
501        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
502        self.assertEquals('groupchat', message.getAttribute('type'))
503        self.assertEquals(u'This is a test', unicode(message.body))
504
505
506    def test_chat(self):
507        """
508        Send private messages to muc entities.
509        """
510        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
511
512        self.protocol.chat(otherOccupantJID, u'This is a test')
513
514        message = self.stub.output[-1]
515
516        self.assertEquals('message', message.name)
517        self.assertEquals(otherOccupantJID.full(), message.getAttribute('to'))
518        self.assertEquals('chat', message.getAttribute('type'))
519        self.assertEquals(u'This is a test', unicode(message.body))
520
521
522    def test_register(self):
523        """
524        Client registering with a room.
525
526        http://xmpp.org/extensions/xep-0045.html#register
527        """
528
529        # FIXME: this doesn't really test the registration
530
531        def cb(iq):
532            # check for a result
533            self.assertEquals('result', iq['type'], 'We did not get a result')
534
535        d = self.protocol.register(self.roomJID)
536        d.addCallback(cb)
537
538        iq = self.stub.output[-1]
539        query = "/iq/query[@xmlns='%s']" % muc.NS_REQUEST
540        self.assertTrue(xpath.matches(query, iq), 'Invalid iq register request')
541
542        response = toResponse(iq, 'result')
543        self.stub.send(response)
544        return d
545
546
547    def test_voice(self):
548        """
549        Client requesting voice for a room.
550        """
551        self.protocol.voice(self.occupantJID)
552
553        m = self.stub.output[-1]
554
555        query = ("/message/x[@type='submit']/field/value"
556                    "[text()='%s']") % muc.NS_MUC_REQUEST
557        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
558
559
560    def test_roomConfigure(self):
561        """
562        Default configure and changing the room name.
563        """
564
565        def cb(iq):
566            self.assertEquals('result', iq['type'], 'Not a result')
567
568
569        fields = []
570
571        fields.append(data_form.Field(label='Natural-Language Room Name',
572                                      var='muc#roomconfig_roomname',
573                                      value=self.roomIdentifier))
574
575        d = self.protocol.configure(self.roomJID, fields)
576        d.addCallback(cb)
577
578        iq = self.stub.output[-1]
579        query = "/iq/query[@xmlns='%s']/x"% muc.NS_MUC_OWNER
580        self.assertTrue(xpath.matches(query, iq), 'Bad configure request')
581
582        response = toResponse(iq, 'result')
583        self.stub.send(response)
584        return d
585
586
587    def test_destroy(self):
588        """
589        Destroy a room.
590        """
591
592        def cb(destroyed):
593            self.assertTrue(destroyed, 'Room not destroyed.')
594
595        d = self.protocol.destroy(self.occupantJID)
596        d.addCallback(cb)
597
598        iq = self.stub.output[-1]
599        query = "/iq/query[@xmlns='%s']/destroy"% muc.NS_MUC_OWNER
600        self.assertTrue(xpath.matches(query, iq), 'Bad configure request')
601
602        response = toResponse(iq, 'result')
603        self.stub.send(response)
604        return d
605
606
607    def test_subject(self):
608        """
609        Change subject of the room.
610        """
611        self.protocol.subject(self.roomJID, u'This is a test')
612
613        message = self.stub.output[-1]
614
615        self.assertEquals('message', message.name)
616        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
617        self.assertEquals('groupchat', message.getAttribute('type'))
618        self.assertEquals(u'This is a test', unicode(message.subject))
619
620
621    def test_nick(self):
622        """
623        Send a nick change to the server.
624        """
625        newNick = 'newNick'
626
627        self._createRoom()
628
629        def cb(room):
630            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
631            self.assertEquals(newNick, room.nick)
632
633        d = self.protocol.nick(self.roomJID, newNick)
634        d.addCallback(cb)
635
636        element = self.stub.output[-1]
637        self.assertEquals('presence', element.name, "Need to be presence")
638        self.assertNotIdentical(None, element.x, 'No muc x element')
639
640        # send back user presence, nick changed
641        xml = u"""
642            <presence from='%s/%s'>
643              <x xmlns='http://jabber.org/protocol/muc#user'>
644                <item affiliation='member' role='participant'/>
645              </x>
646            </presence>
647        """ % (self.roomJID, newNick)
648        self.stub.send(parseXml(xml))
649        return d
650
651
652    def test_nickConflict(self):
653        """
654        If the server finds the new nick in conflict, the errback is called.
655        """
656        newNick = 'newNick'
657
658        self._createRoom()
659
660        d = self.protocol.nick(self.roomJID, newNick)
661        self.assertFailure(d, StanzaError)
662
663        element = self.stub.output[-1]
664        self.assertEquals('presence', element.name, "Need to be presence")
665        self.assertNotIdentical(None, element.x, 'No muc x element')
666
667        # send back user presence, nick changed
668        xml = u"""
669            <presence from='%s/%s' type='error'>
670                <x xmlns='http://jabber.org/protocol/muc'/>
671                <error type='cancel'>
672                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
673                </error>
674            </presence>
675        """ % (self.roomJID, newNick)
676        self.stub.send(parseXml(xml))
677        return d
678
679
680    def test_grantVoice(self):
681        """
682        Test granting voice to a user.
683
684        """
685        nick = 'TroubleMaker'
686        def cb(give_voice):
687            self.assertTrue(give_voice, 'Did not give voice user')
688
689        d = self.protocol.grantVoice(self.occupantJID, nick,
690                                     sender=self.userJID)
691        d.addCallback(cb)
692
693        iq = self.stub.output[-1]
694
695        query = (u"/iq[@type='set' and @to='%s']/query/item"
696                     "[@role='participant']") % self.roomJID
697        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
698
699        response = toResponse(iq, 'result')
700        self.stub.send(response)
701        return d
702
703
704    def test_status(self):
705        """
706        Change status
707        """
708        self._createRoom()
709        room = self.protocol._getRoom(self.roomJID)
710        user = muc.User(self.nick)
711        room.addUser(user)
712
713        def cb(room):
714            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
715            user = room.getUser(self.nick)
716            self.assertNotIdentical(None, user, 'User not found')
717            self.assertEquals('testing MUC', user.status, 'Wrong status')
718            self.assertEquals('xa', user.show, 'Wrong show')
719
720        d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
721        d.addCallback(cb)
722
723        element = self.stub.output[-1]
724
725        self.assertEquals('presence', element.name, "Need to be presence")
726        self.assertTrue(getattr(element, 'x', None), 'No muc x element')
727
728        # send back user presence, status changed
729        xml = u"""
730            <presence from='%s'>
731              <x xmlns='http://jabber.org/protocol/muc#user'>
732                <item affiliation='member' role='participant'/>
733              </x>
734              <show>xa</show>
735              <status>testing MUC</status>
736            </presence>
737        """ % self.occupantJID
738        self.stub.send(parseXml(xml))
739        return d
740
741
742    def test_getMemberList(self):
743        def cb(room):
744            members = room.members
745            self.assertEquals(1, len(members))
746            user = members[0]
747            self.assertEquals(JID(u'hag66@shakespeare.lit'), user.entity)
748            self.assertEquals(u'thirdwitch', user.nick)
749            self.assertEquals(u'participant', user.role)
750
751        self._createRoom()
752        d = self.protocol.getMemberList(self.roomJID)
753        d.addCallback(cb)
754
755        iq = self.stub.output[-1]
756        query = iq.query
757        self.assertNotIdentical(None, query)
758        self.assertEquals(NS_MUC_ADMIN, query.uri)
759
760        response = toResponse(iq, 'result')
761        query = response.addElement((NS_MUC_ADMIN, 'query'))
762        item = query.addElement('item')
763        item['affiliation'] ='member'
764        item['jid'] = 'hag66@shakespeare.lit'
765        item['nick'] = 'thirdwitch'
766        item['role'] = 'participant'
767        self.stub.send(response)
768
769        return d
Note: See TracBrowser for help on using the repository browser.