source: wokkel/test/test_muc.py @ 144:6786830f3cd5

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

Cleanup some tests, add a few tests, fix subject.

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