source: wokkel/test/test_muc.py @ 141:3a9bff4e7807

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

Use descendants from wokkel.generic.Stanza in messages and presence.

  • Use wokkel.xmppim.AvailabilityPresence as a basis for presence stanzas.
  • Split wokkel.xmppim.PresenceProtocol and Base MUCClient on BasePresenceProtocol
  • Add new wokkel.xmppim.Message as the basis for message stanzas.
  • `receivedGroupChat
  • Change order of parameters for invite, as reason is optional.
  • Use datetimes everywhere where timestamps are needed.
File size: 18.5 KB
Line 
1# Copyright (c) 2003-2009 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.test_room = 'test'
51        self.test_srv  = 'conference.example.org'
52        self.test_nick = 'Nick'
53
54        self.room_jid = JID(tuple=(self.test_room,
55                                   self.test_srv,
56                                   self.test_nick))
57        self.user_jid = JID('test@jabber.org/Testing')
58
59
60    def _createRoom(self):
61        """
62        A helper method to create a test room.
63        """
64        # create a room
65        self.current_room = muc.Room(self.test_room,
66                                     self.test_srv,
67                                     self.test_nick)
68        self.protocol._addRoom(self.current_room)
69
70
71    def test_interface(self):
72        """
73        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
74        """
75        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
76
77
78    def test_userJoinedRoom(self):
79        """
80        The client receives presence from an entity joining the room.
81
82        This tests the class L{muc.UserPresence} and the userJoinedRoom event method.
83
84        The test sends the user presence and tests if the event method is called.
85
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.user_jid.full(), self.room_jid.full())
94
95        # create a room
96        self._createRoom()
97
98        def userJoinedRoom(room, user):
99            self.assertEquals(self.test_room, 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_groupChat(self):
109        """
110        The client receives a groupchat message from an entity in the room.
111        """
112        xml = """
113            <message to='test@test.com' from='%s' type='groupchat'>
114              <body>test</body>
115            </message>
116        """ % (self.room_jid.full())
117
118        self._createRoom()
119
120        def groupChat(room, user, message):
121            self.assertEquals('test', message.body, "Wrong group chat message")
122            self.assertEquals(self.test_room, room.roomIdentifier,
123                              'Wrong room name')
124
125        d, self.protocol.receivedGroupChat = calledAsync(groupChat)
126        self.stub.send(parseXml(xml))
127        return d
128
129
130    def test_joinRoom(self):
131        """
132        Joining a room
133        """
134
135        def cb(room):
136            self.assertEquals(self.test_room, room.roomIdentifier)
137
138        d = self.protocol.join(self.test_srv, self.test_room, self.test_nick)
139        d.addCallback(cb)
140
141        prs = self.stub.output[-1]
142        self.assertEquals('presence', prs.name, "Need to be presence")
143        self.assertNotIdentical(None, prs.x, 'No muc x element')
144
145        # send back user presence, they joined
146        xml = """
147            <presence from='%s@%s/%s'>
148              <x xmlns='http://jabber.org/protocol/muc#user'>
149                <item affiliation='member' role='participant'/>
150              </x>
151            </presence>
152        """ % (self.test_room, self.test_srv, self.test_nick)
153        self.stub.send(parseXml(xml))
154        return d
155
156
157    def test_joinRoomForbidden(self):
158        """
159        Client joining a room and getting a forbidden error.
160        """
161
162        def cb(error):
163            self.assertEquals('forbidden', error.value.condition,
164                              'Wrong muc condition')
165
166        d = self.protocol.join(self.test_srv, self.test_room, self.test_nick)
167        d.addBoth(cb)
168
169        prs = self.stub.output[-1]
170        self.assertEquals('presence', prs.name, "Need to be presence")
171        self.assertNotIdentical(None, prs.x, 'No muc x element')
172
173        # send back error, forbidden
174        xml = """
175            <presence from='%s' type='error'>
176              <error type='auth'>
177                <forbidden xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
178              </error>
179            </presence>
180        """ % (self.room_jid.full())
181        self.stub.send(parseXml(xml))
182        return d
183
184
185    def test_joinRoomBadJID(self):
186        """
187        Client joining a room and getting a jid-malformed error.
188        """
189
190        def cb(error):
191            self.assertEquals('jid-malformed', error.value.condition,
192                              'Wrong muc condition')
193
194        d = self.protocol.join(self.test_srv, self.test_room, self.test_nick)
195        d.addBoth(cb)
196
197        prs = self.stub.output[-1]
198        self.assertEquals('presence', prs.name, "Need to be presence")
199        self.assertNotIdentical(None, prs.x, 'No muc x element')
200
201        # send back error, bad JID
202        xml = """
203            <presence from='%s' type='error'>
204              <error type='modify'>
205                <jid-malformed xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
206              </error>
207            </presence>
208        """ % (self.room_jid.full())
209        self.stub.send(parseXml(xml))
210        return d
211
212
213    def test_partRoom(self):
214        """
215        Client leaves a room
216        """
217        def cb(left):
218            self.assertTrue(left, 'did not leave room')
219
220        self._createRoom()
221        d = self.protocol.leave(self.room_jid)
222        d.addCallback(cb)
223
224        prs = self.stub.output[-1]
225
226        self.assertEquals('unavailable', prs['type'],
227                          'Unavailable is not being sent')
228
229        xml = """
230            <presence to='test@jabber.org' from='%s' type='unavailable'/>
231        """ % (self.room_jid.full())
232        self.stub.send(parseXml(xml))
233        return d
234
235
236    def test_userPartsRoom(self):
237        """
238        An entity leaves the room, a presence of type unavailable is received by the client.
239        """
240
241        xml = """
242            <presence to='%s' from='%s' type='unavailable'/>
243        """ % (self.user_jid.full(), self.room_jid.full())
244
245        # create a room
246        self._createRoom()
247
248        # add user to room
249        user = muc.User(self.room_jid.resource)
250        room = self.protocol._getRoom(self.room_jid)
251        room.addUser(user)
252
253        def userPresence(room, user):
254            self.assertEquals(self.test_room, room.roomIdentifier,
255                              'Wrong room name')
256            self.assertFalse(room.inRoster(user), 'User in roster')
257
258        d, self.protocol.userLeftRoom = calledAsync(userPresence)
259        self.stub.send(parseXml(xml))
260        return d
261
262
263    def test_ban(self):
264        """
265        Ban an entity in a room.
266        """
267        banned = JID('ban@jabber.org/TroubleMaker')
268
269        def cb(banned):
270            self.assertTrue(banned, 'Did not ban user')
271
272        d = self.protocol.ban(self.room_jid, banned, reason='Spam',
273                              sender=self.user_jid)
274        d.addCallback(cb)
275
276        iq = self.stub.output[-1]
277
278        self.assertTrue(xpath.matches(
279                "/iq[@type='set' and @to='%s']/query/item"
280                    "[@affiliation='outcast']" % (self.room_jid.userhost(),),
281                iq),
282            'Wrong ban stanza')
283
284        response = toResponse(iq, 'result')
285        self.stub.send(response)
286
287        return d
288
289
290    def test_kick(self):
291        """
292        Kick an entity from a room.
293        """
294        nick = 'TroubleMaker'
295
296        def cb(kicked):
297            self.assertTrue(kicked, 'Did not kick user')
298
299        d = self.protocol.kick(self.room_jid, nick, reason='Spam',
300                               sender=self.user_jid)
301        d.addCallback(cb)
302
303        iq = self.stub.output[-1]
304
305        self.assertTrue(xpath.matches(
306                "/iq[@type='set' and @to='%s']/query/item"
307                    "[@affiliation='none']" % (self.room_jid.userhost(),),
308                iq),
309            'Wrong kick stanza')
310
311        response = toResponse(iq, 'result')
312        self.stub.send(response)
313
314        return d
315
316
317    def test_password(self):
318        """
319        Sending a password via presence to a password protected room.
320        """
321
322        self.protocol.password(self.room_jid, 'secret')
323
324        prs = self.stub.output[-1]
325
326        self.assertTrue(xpath.matches(
327                "/presence[@to='%s']/x/password"
328                    "[text()='secret']" % (self.room_jid.full(),),
329                prs),
330            'Wrong presence stanza')
331
332
333    def test_historyReceived(self):
334        """
335        Receiving history on room join.
336        """
337        xml = """
338            <message to='test@test.com' from='%s' type='groupchat'>
339              <body>test</body>
340              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
341                                            from="%s"/>
342            </message>
343        """ % (self.room_jid.full(), self.user_jid.full())
344
345        self._createRoom()
346
347
348        def historyReceived(room, user, message):
349            self.assertEquals('test', message.body, "wrong message body")
350            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
351            self.assertEquals(stamp, message.delay.stamp,
352                             'Does not have a history stamp')
353
354        d, self.protocol.receivedHistory = calledAsync(historyReceived)
355        self.stub.send(parseXml(xml))
356        return d
357
358
359    def test_oneToOneChat(self):
360        """
361        Converting a one to one chat to a multi-user chat.
362        """
363        archive = []
364        thread = "e0ffe42b28561960c6b12b944a092794b9683a38"
365        # create messages
366        msg = domish.Element((None, 'message'))
367        msg['to'] = 'testing@example.com'
368        msg['type'] = 'chat'
369        msg.addElement('body', None, 'test')
370        msg.addElement('thread', None, thread)
371
372        archive.append({'stanza': msg,
373                        'timestamp': datetime(2002, 10, 13, 23, 58, 37,
374                                              tzinfo=tzutc())})
375
376        msg = domish.Element((None, 'message'))
377        msg['to'] = 'testing2@example.com'
378        msg['type'] = 'chat'
379        msg.addElement('body', None, 'yo')
380        msg.addElement('thread', None, thread)
381
382        archive.append({'stanza': msg,
383                        'timestamp': datetime(2002, 10, 13, 23, 58, 43,
384                                              tzinfo=tzutc())})
385
386        self.protocol.history(self.room_jid, archive)
387
388
389        while len(self.stub.output)>0:
390            m = self.stub.output.pop()
391            # check for delay element
392            self.assertEquals('message', m.name, 'Wrong stanza')
393            self.assertTrue(xpath.matches("/message/delay", m), 'Invalid history stanza')
394
395
396    def test_invite(self):
397        """
398        Invite a user to a room
399        """
400        bareRoomJID = self.room_jid.userhostJID()
401        invitee = JID('test@jabber.org')
402
403        self.protocol.invite(bareRoomJID, invitee, 'This is a test')
404
405        msg = self.stub.output[-1]
406
407        query = u"/message[@to='%s']/x/invite/reason" % bareRoomJID
408        self.assertTrue(xpath.matches(query, msg), 'Wrong message type')
409
410
411    def test_privateMessage(self):
412        """
413        Send private messages to muc entities.
414        """
415        other_nick = JID(self.room_jid.userhost()+'/OtherNick')
416
417        self.protocol.chat(other_nick, 'This is a test')
418
419        msg = self.stub.output[-1]
420
421        query = "/message[@type='chat' and @to='%s']/body" % other_nick.full()
422        self.assertTrue(xpath.matches(query, msg), 'Wrong message type')
423
424
425    def test_register(self):
426        """
427        Client registering with a room.
428
429        http://xmpp.org/extensions/xep-0045.html#register
430        """
431
432        def cb(iq):
433            # check for a result
434            self.assertEquals('result', iq['type'], 'We did not get a result')
435
436        d = self.protocol.register(self.room_jid)
437        d.addCallback(cb)
438
439        iq = self.stub.output[-1]
440        query = "/iq/query[@xmlns='%s']" % muc.NS_REQUEST
441        self.assertTrue(xpath.matches(query, iq), 'Invalid iq register request')
442
443        response = toResponse(iq, 'result')
444        self.stub.send(response)
445        return d
446
447
448    def test_voice(self):
449        """
450        Client requesting voice for a room.
451        """
452        self.protocol.voice(self.room_jid)
453
454        m = self.stub.output[-1]
455
456        query = ("/message/x[@type='submit']/field/value"
457                    "[text()='%s']") % muc.NS_MUC_REQUEST
458        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
459
460
461    def test_roomConfigure(self):
462        """
463        Default configure and changing the room name.
464        """
465
466        def cb(iq):
467            self.assertEquals('result', iq['type'], 'Not a result')
468
469
470        fields = []
471
472        fields.append(data_form.Field(label='Natural-Language Room Name',
473                                      var='muc#roomconfig_roomname',
474                                      value=self.test_room))
475
476        d = self.protocol.configure(self.room_jid.userhost(), fields)
477        d.addCallback(cb)
478
479        iq = self.stub.output[-1]
480        query = "/iq/query[@xmlns='%s']/x"% muc.NS_MUC_OWNER
481        self.assertTrue(xpath.matches(query, iq), 'Bad configure request')
482
483        response = toResponse(iq, 'result')
484        self.stub.send(response)
485        return d
486
487
488    def test_roomDestroy(self):
489        """
490        Destroy a room.
491        """
492
493        def cb(destroyed):
494            self.assertTrue(destroyed, 'Room not destroyed.')
495
496        d = self.protocol.destroy(self.room_jid)
497        d.addCallback(cb)
498
499        iq = self.stub.output[-1]
500        query = "/iq/query[@xmlns='%s']/destroy"% muc.NS_MUC_OWNER
501        self.assertTrue(xpath.matches(query, iq), 'Bad configure request')
502
503        response = toResponse(iq, 'result')
504        self.stub.send(response)
505        return d
506
507
508    def test_nickChange(self):
509        """
510        Send a nick change to the server.
511        """
512        test_nick = 'newNick'
513
514        self._createRoom()
515
516        def cb(room):
517            self.assertEquals(self.test_room, room.roomIdentifier)
518            self.assertEquals(test_nick, room.nick)
519
520        d = self.protocol.nick(self.room_jid, test_nick)
521        d.addCallback(cb)
522
523        prs = self.stub.output[-1]
524        self.assertEquals('presence', prs.name, "Need to be presence")
525        self.assertNotIdentical(None, prs.x, 'No muc x element')
526
527        # send back user presence, nick changed
528        xml = """
529            <presence from='%s@%s/%s'>
530              <x xmlns='http://jabber.org/protocol/muc#user'>
531                <item affiliation='member' role='participant'/>
532              </x>
533            </presence>
534        """ % (self.test_room, self.test_srv, test_nick)
535        self.stub.send(parseXml(xml))
536        return d
537
538
539    def test_grantVoice(self):
540        """
541        Test granting voice to a user.
542
543        """
544        nick = 'TroubleMaker'
545        def cb(give_voice):
546            self.assertTrue(give_voice, 'Did not give voice user')
547
548        d = self.protocol.grantVoice(self.room_jid, nick, sender=self.user_jid)
549        d.addCallback(cb)
550
551        iq = self.stub.output[-1]
552
553        query = ("/iq[@type='set' and @to='%s']/query/item"
554                     "[@role='participant']") % self.room_jid.userhost()
555        self.assertTrue(xpath.matches(query, iq), 'Wrong voice stanza')
556
557        response = toResponse(iq, 'result')
558        self.stub.send(response)
559        return d
560
561
562    def test_changeStatus(self):
563        """
564        Change status
565        """
566        self._createRoom()
567        room = self.protocol._getRoom(self.room_jid)
568        user = muc.User(self.room_jid.resource)
569        room.addUser(user)
570
571        def cb(room):
572            self.assertEquals(self.test_room, room.roomIdentifier)
573            user = room.getUser(self.room_jid.resource)
574            self.assertNotIdentical(None, user, 'User not found')
575            self.assertEquals('testing MUC', user.status, 'Wrong status')
576            self.assertEquals('xa', user.show, 'Wrong show')
577
578        d = self.protocol.status(self.room_jid, 'xa', 'testing MUC')
579        d.addCallback(cb)
580
581        prs = self.stub.output[-1]
582
583        self.assertEquals('presence', prs.name, "Need to be presence")
584        self.assertTrue(getattr(prs, 'x', None), 'No muc x element')
585
586        # send back user presence, status changed
587        xml = """
588            <presence from='%s'>
589              <x xmlns='http://jabber.org/protocol/muc#user'>
590                <item affiliation='member' role='participant'/>
591              </x>
592              <show>xa</show>
593              <status>testing MUC</status>
594            </presence>
595        """ % self.room_jid.full()
596        self.stub.send(parseXml(xml))
597        return d
598
599
600    def test_getMemberList(self):
601        def cb(room):
602            members = room.members
603            self.assertEquals(1, len(members))
604            user = members[0]
605            self.assertEquals(JID(u'hag66@shakespeare.lit'), user.entity)
606            self.assertEquals(u'thirdwitch', user.nick)
607            self.assertEquals(u'participant', user.role)
608
609        self._createRoom()
610        bareRoomJID = self.room_jid.userhostJID()
611        d = self.protocol.getMemberList(bareRoomJID)
612        d.addCallback(cb)
613
614        iq = self.stub.output[-1]
615        query = iq.query
616        self.assertNotIdentical(None, query)
617        self.assertEquals(NS_MUC_ADMIN, query.uri)
618
619        response = toResponse(iq, 'result')
620        query = response.addElement((NS_MUC_ADMIN, 'query'))
621        item = query.addElement('item')
622        item['affiliation'] ='member'
623        item['jid'] = 'hag66@shakespeare.lit'
624        item['nick'] = 'thirdwitch'
625        item['role'] = 'participant'
626        self.stub.send(response)
627
628        return d
629
630
Note: See TracBrowser for help on using the repository browser.