source: wokkel/test/test_muc.py @ 147:aa2cfee614e7

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

Use generic.Request for room destruction request, add TestableStreamManager?.

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