Changeset 152:ae3907d81abc for wokkel


Ignore:
Timestamp:
Aug 18, 2011, 3:22:45 PM (9 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
wokkel-muc-client-support-24
Message:

Split protocol and room/user administration into two classes.

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

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

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

Location:
wokkel
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • wokkel/iwokkel.py

    r151 r152  
    825825
    826826
    827     def join(service, roomIdentifier, nick, history=None):
     827    def join(roomJID, nick, historyOptions=None, password=None):
    828828        """
    829829        Join a MUC room by sending presence to it.
    830830
    831         @param server: The server where the room is located.
    832         @type server: C{unicode}
    833 
    834         @param room: The room name the entity is joining.
    835         @type room: C{unicode}
     831        @param roomJID: The JID of the room the entity is joining.
     832        @type roomJID: L{jid.JID}
    836833
    837834        @param nick: The nick name for the entitity joining the room.
    838835        @type nick: C{unicode}
    839836
    840         @param history: The maximum number of history stanzas you would like.
     837        @param historyOptions: Options for conversation history sent by the
     838            room upon joining.
     839        @type historyOptions: L{HistoryOptions}
     840
     841        @param password: Optional password for the room.
     842        @type password: C{unicode}
    841843
    842844        @return: A deferred that fires when the entity is in the room or an
     
    885887
    886888
    887     def groupChat(roomJID, body, children=None):
     889    def groupChat(roomJID, body):
    888890        """
    889891        Send a groupchat message.
     
    891893
    892894
    893     def chat(occupantJID, body, children=None):
     895    def chat(occupantJID, body):
    894896        """
    895897        Send a private chat message to a user in a MUC room.
     
    899901        @param occupantJID: The Room JID of the other user.
    900902        @type occupantJID: L{jid.JID}
    901         """
    902 
    903 
    904     def password(roomJID, password):
    905         """
    906         Send a password to a room so the entity can join.
    907 
    908         See: http://xmpp.org/extensions/xep-0045.html#enter-pw
    909 
    910         @param roomJID: The bare JID of the room.
    911         @type roomJID: L{jid.JID}
    912 
    913         @param password: The MUC room password.
    914         @type password: C{unicode}
    915903        """
    916904
  • wokkel/muc.py

    r151 r152  
    244244
    245245        if self.delay:
    246             element.addChild(self.delay.toElement())
     246            element.addChild(self.delay.toElement(legacy=legacyDelay))
    247247
    248248        return element
     
    300300    @type since: L{datetime.datetime}
    301301    """
    302     attributes = ['maxchars', 'maxstanzas', 'seconds', 'since']
    303 
    304     def __init__(self, maxchars=None, maxstanzas=None, seconds=None,
     302    attributes = ['maxChars', 'maxStanzas', 'seconds', 'since']
     303
     304    def __init__(self, maxChars=None, maxStanzas=None, seconds=None,
    305305                       since=None):
    306         self.maxchars = maxchars
    307         self.maxstanzas = maxstanzas
     306        self.maxChars = maxChars
     307        self.maxStanzas = maxStanzas
    308308        self.seconds = seconds
    309309        self.since = since
     
    321321                if key == 'since':
    322322                    stamp = value.astimezone(tzutc())
    323                     element[key] = stamp.strftime('%Y%m%dT%H:%M:%SZ')
     323                    element[key] = stamp.strftime('%Y-%m-%dT%H:%M:%SZ')
    324324                else:
    325                     element[key] = str(value)
     325                    element[key.lower()] = str(value)
    326326
    327327        return element
    328 
    329 
    330 
    331 class User(object):
    332     """
    333     A user/entity in a multi-user chat room.
    334     """
    335 
    336     def __init__(self, nick, entity=None):
    337         self.nick = nick
    338         self.entity = entity
    339         self.affiliation = 'none'
    340         self.role = 'none'
    341 
    342         self.status = None
    343         self.show = None
    344 
    345 
    346 
    347 class Room(object):
    348     """
    349     A Multi User Chat Room.
    350 
    351     An in memory object representing a MUC room from the perspective of
    352     a client.
    353 
    354     @ivar roomIdentifier: The Room ID of the MUC room.
    355     @type roomIdentifier: C{unicode}
    356 
    357     @ivar service: The server where the MUC room is located.
    358     @type service: C{unicode}
    359 
    360     @ivar nick: The nick name for the client in this room.
    361     @type nick: C{unicode}
    362 
    363     @ivar state: The status code of the room.
    364     @type state: L{int}
    365 
    366     @ivar occupantJID: The JID of the occupant in the room. Generated from
    367         roomIdentifier, service, and nick.
    368     @type occupantJID: L{jid.JID}
    369     """
    370 
    371 
    372     def __init__(self, roomIdentifier, service, nick, state=None):
    373         """
    374         Initialize the room.
    375         """
    376         self.roomIdentifier = roomIdentifier
    377         self.service = service
    378         self.setNick(nick)
    379         self.state = state
    380 
    381         self.status = 0
    382 
    383         self.roster = {}
    384 
    385 
    386     def setNick(self, nick):
    387         self.occupantJID = jid.internJID(u"%s@%s/%s" % (self.roomIdentifier,
    388                                                         self.service,
    389                                                         nick))
    390         self.nick = nick
    391 
    392 
    393     def addUser(self, user):
    394         """
    395         Add a user to the room roster.
    396 
    397         @param user: The user object that is being added to the room.
    398         @type user: L{User}
    399         """
    400         self.roster[user.nick] = user
    401 
    402 
    403     def inRoster(self, user):
    404         """
    405         Check if a user is in the MUC room.
    406 
    407         @param user: The user object to check.
    408         @type user: L{User}
    409         """
    410 
    411         return user.nick in self.roster
    412 
    413 
    414     def getUser(self, nick):
    415         """
    416         Get a user from the room's roster.
    417 
    418         @param nick: The nick for the user in the MUC room.
    419         @type nick: C{unicode}
    420         """
    421         return self.roster.get(nick)
    422 
    423 
    424     def removeUser(self, user):
    425         """
    426         Remove a user from the MUC room's roster.
    427 
    428         @param user: The user object to check.
    429         @type user: L{User}
    430         """
    431         if self.inRoster(user):
    432             del self.roster[user.nick]
    433328
    434329
     
    494389
    495390
    496 class MUCClient(xmppim.BasePresenceProtocol):
     391class MUCClientProtocol(xmppim.BasePresenceProtocol):
    497392    """
    498393    Multi-User Chat client protocol.
    499 
    500     This is a subclass of L{XMPPHandler} and implements L{IMUCCLient}.
    501 
    502     @ivar _rooms: Collection of occupied rooms, keyed by the bare JID of the
    503                   room. Note that a particular entity can only join a room once
    504                   at a time.
    505     @type _rooms: C{dict}
    506     """
    507 
    508     implements(IMUCClient)
     394    """
    509395
    510396    timeout = None
     
    519405        XMPPHandler.__init__(self)
    520406
    521         self._rooms = {}
    522         self._deferreds = []
    523 
    524407        if reactor:
    525408            self._reactor = reactor
     
    539422        xmppim.BasePresenceProtocol.connectionInitialized(self)
    540423        self.xmlstream.addObserver(GROUPCHAT, self._onGroupChat)
    541         self.initialized()
     424        self._roomOccupantMap = {}
     425
     426
     427    def _onGroupChat(self, element):
     428        """
     429        A group chat message has been received from a MUC room.
     430
     431        There are a few event methods that may get called here.
     432        L{receivedGroupChat}, L{receivedHistory} or L{receivedHistory}.
     433        """
     434        message = GroupChat.fromElement(element)
     435        self.groupChatReceived(message)
     436
     437
     438    def groupChatReceived(self, message):
     439        """
     440        Called when a groupchat message was received.
     441
     442        This method is called with a parsed representation of a received
     443        groupchat message and can be overridden for further processing.
     444
     445        For regular groupchat message, the C{body} attribute contains the
     446        message body. Conversation history sent by the room upon joining, will
     447        have the C{delay} attribute set, room subject changes the C{subject}
     448        attribute. See L{GroupChat} for details.
     449
     450        @param message: Groupchat message.
     451        @type message: L{GroupChat}
     452        """
     453        pass
     454
     455
     456    def _sendDeferred(self, stanza):
     457        """
     458        Send presence stanza, adding a deferred with a timeout.
     459
     460        @param stanza: The presence stanza to send over the wire.
     461        @type stanza: L{generic.Stanza}
     462
     463        @param timeout: The number of seconds to wait before the deferred is
     464            timed out.
     465        @type timeout: L{int}
     466
     467        The deferred object L{defer.Deferred} is returned.
     468        """
     469        def onResponse(element):
     470            if element.getAttribute('type') == 'error':
     471                d.errback(error.exceptionFromStanza(element))
     472            else:
     473                d.callback(UserPresence.fromElement(element))
     474
     475        def onTimeout():
     476            d.errback(xmlstream.TimeoutError("Timeout waiting for response."))
     477
     478        def cancelTimeout(result):
     479            if call.active():
     480                call.cancel()
     481
     482            return result
     483
     484        def recordOccupant(presence):
     485            occupantJID = presence.sender
     486            roomJID = occupantJID.userhostJID()
     487            self._roomOccupantMap[roomJID] = occupantJID
     488            return presence
     489
     490        call = self._reactor.callLater(DEFER_TIMEOUT, onTimeout)
     491
     492        d = defer.Deferred()
     493        d.addBoth(cancelTimeout)
     494        d.addCallback(recordOccupant)
     495
     496        query = "/presence[@from='%s' or (@from='%s' and @type='error')]" % (
     497                stanza.recipient.full(), stanza.recipient.userhost())
     498        self.xmlstream.addOnetimeObserver(query, onResponse, priority=1)
     499        self.xmlstream.send(stanza.toElement())
     500        return d
     501
     502
     503    def join(self, roomJID, nick, historyOptions=None, password=None):
     504        """
     505        Join a MUC room by sending presence to it.
     506
     507        @param roomJID: The JID of the room the entity is joining.
     508        @type roomJID: L{jid.JID}
     509
     510        @param nick: The nick name for the entitity joining the room.
     511        @type nick: C{unicode}
     512
     513        @param historyOptions: Options for conversation history sent by the
     514            room upon joining.
     515        @type historyOptions: L{HistoryOptions}
     516
     517        @param password: Optional password for the room.
     518        @type password: C{unicode}
     519
     520        @return: A deferred that fires when the entity is in the room or an
     521                 error has occurred.
     522        """
     523        occupantJID = jid.JID(tuple=(roomJID.user, roomJID.host, nick))
     524
     525        presence = BasicPresence(recipient=occupantJID)
     526        if password:
     527            presence.password = password
     528        if historyOptions:
     529            presence.history = historyOptions
     530
     531        return self._sendDeferred(presence)
     532
     533
     534    def nick(self, roomJID, nick):
     535        """
     536        Change an entity's nick name in a MUC room.
     537
     538        See: http://xmpp.org/extensions/xep-0045.html#changenick
     539
     540        @param roomJID: The JID of the room.
     541        @type roomJID: L{jid.JID}
     542
     543        @param nick: The new nick name within the room.
     544        @type nick: C{unicode}
     545        """
     546        occupantJID = jid.JID(tuple=(roomJID.user, roomJID.host, nick))
     547        presence = BasicPresence(recipient=occupantJID)
     548        return self._sendDeferred(presence)
     549
     550
     551    def status(self, roomJID, show=None, status=None):
     552        """
     553        Change user status.
     554
     555        See: http://xmpp.org/extensions/xep-0045.html#changepres
     556
     557        @param roomJID: The Room JID of the room.
     558        @type roomJID: L{jid.JID}
     559
     560        @param show: The availability of the entity. Common values are xa,
     561            available, etc
     562        @type show: C{unicode}
     563
     564        @param status: The current status of the entity.
     565        @type status: C{unicode}
     566        """
     567        occupantJID = self._roomOccupantMap[roomJID]
     568        presence = BasicPresence(recipient=occupantJID, show=show,
     569                                 status=status)
     570        return self._sendDeferred(presence)
     571
     572
     573    def leave(self, roomJID):
     574        """
     575        Leave a MUC room.
     576
     577        See: http://xmpp.org/extensions/xep-0045.html#exit
     578
     579        @param roomJID: The JID of the room.
     580        @type roomJID: L{jid.JID}
     581        """
     582        occupantJID = self._roomOccupantMap[roomJID]
     583        presence = xmppim.AvailabilityPresence(recipient=occupantJID,
     584                                               available=False)
     585
     586        return self._sendDeferred(presence)
     587
     588
     589    def groupChat(self, roomJID, body):
     590        """
     591        Send a groupchat message.
     592        """
     593        message = GroupChat(recipient=roomJID, body=body)
     594        self.send(message.toElement())
     595
     596
     597    def chat(self, occupantJID, body):
     598        """
     599        Send a private chat message to a user in a MUC room.
     600
     601        See: http://xmpp.org/extensions/xep-0045.html#privatemessage
     602
     603        @param occupantJID: The Room JID of the other user.
     604        @type occupantJID: L{jid.JID}
     605        """
     606        message = PrivateChat(recipient=occupantJID, body=body)
     607        self.send(message.toElement())
     608
     609
     610    def subject(self, roomJID, subject):
     611        """
     612        Change the subject of a MUC room.
     613
     614        See: http://xmpp.org/extensions/xep-0045.html#subject-mod
     615
     616        @param roomJID: The bare JID of the room.
     617        @type roomJID: L{jid.JID}
     618
     619        @param subject: The subject you want to set.
     620        @type subject: C{unicode}
     621        """
     622        message = GroupChat(roomJID.userhostJID(), subject=subject)
     623        self.send(message.toElement())
     624
     625
     626    def invite(self, roomJID, invitee, reason=None):
     627        """
     628        Invite a xmpp entity to a MUC room.
     629
     630        See: http://xmpp.org/extensions/xep-0045.html#invite
     631
     632        @param roomJID: The bare JID of the room.
     633        @type roomJID: L{jid.JID}
     634
     635        @param invitee: The entity that is being invited.
     636        @type invitee: L{jid.JID}
     637
     638        @param reason: The reason for the invite.
     639        @type reason: C{unicode}
     640        """
     641        message = InviteMessage(recipient=roomJID, invitee=invitee,
     642                                reason=reason)
     643        self.send(message.toElement())
     644
     645
     646    def getRegisterForm(self, roomJID):
     647        """
     648        Grab the registration form for a MUC room.
     649
     650        @param room: The room jabber/xmpp entity id for the requested
     651            registration form.
     652        @type room: L{jid.JID}
     653        """
     654        def cb(response):
     655            form = data_form.findForm(response.query, NS_MUC_REGISTER)
     656            return form
     657
     658        request = RegisterRequest(recipient=roomJID, options=None)
     659        d = self.request(request)
     660        d.addCallback(cb)
     661        return d
     662
     663
     664    def register(self, roomJID, options):
     665        """
     666        Send a request to register for a room.
     667
     668        @param roomJID: The bare JID of the room.
     669        @type roomJID: L{jid.JID}
     670
     671        @param options: A mapping of field names to values, or C{None} to
     672            cancel.
     673        @type options: C{dict}
     674        """
     675        request = RegisterRequest(recipient=roomJID, options=options)
     676        return self.request(request)
     677
     678
     679    def voice(self, roomJID):
     680        """
     681        Request voice for a moderated room.
     682
     683        @param roomJID: The room jabber/xmpp entity id.
     684        @type roomJID: L{jid.JID}
     685        """
     686        message = VoiceRequest(recipient=roomJID)
     687        self.xmlstream.send(message.toElement())
     688
     689
     690    def history(self, roomJID, messages):
     691        """
     692        Send history to create a MUC based on a one on one chat.
     693
     694        See: http://xmpp.org/extensions/xep-0045.html#continue
     695
     696        @param roomJID: The room jabber/xmpp entity id.
     697        @type roomJID: L{jid.JID}
     698
     699        @param messages: The history to send to the room as an ordered list of
     700                         message, represented by a dictionary with the keys
     701                         C{'stanza'}, holding the original stanza a
     702                         L{domish.Element}, and C{'timestamp'} with the
     703                         timestamp.
     704        @type messages: L{list} of L{domish.Element}
     705        """
     706
     707        for message in messages:
     708            stanza = message['stanza']
     709            stanza['type'] = 'groupchat'
     710
     711            delay = Delay(stamp=message['timestamp'])
     712
     713            sender = stanza.getAttribute('from')
     714            if sender is not None:
     715                delay.sender = jid.JID(sender)
     716
     717            stanza.addChild(delay.toElement())
     718
     719            stanza['to'] = roomJID.userhost()
     720            if stanza.hasAttribute('from'):
     721                del stanza['from']
     722
     723            self.xmlstream.send(stanza)
     724
     725
     726    def getConfiguration(self, roomJID):
     727        """
     728        Grab the configuration from the room.
     729
     730        This sends an iq request to the room.
     731
     732        @param roomJID: The bare JID of the room.
     733        @type roomJID: L{jid.JID}
     734
     735        @return: A deferred that fires with the room's configuration form as
     736            a L{data_form.Form} or C{None} if there are no configuration
     737            options available.
     738        """
     739        def cb(response):
     740            form = data_form.findForm(response.query, NS_MUC_CONFIG)
     741            return form
     742
     743        request = ConfigureRequest(recipient=roomJID, options=None)
     744        d = self.request(request)
     745        d.addCallback(cb)
     746        return d
     747
     748
     749    def configure(self, roomJID, options):
     750        """
     751        Configure a room.
     752
     753        @param roomJID: The room to configure.
     754        @type roomJID: L{jid.JID}
     755
     756        @param options: A mapping of field names to values, or C{None} to cancel.
     757        @type options: C{dict}
     758        """
     759        if not options:
     760            options = False
     761        request = ConfigureRequest(recipient=roomJID, options=options)
     762        return self.request(request)
     763
     764
     765    def _getAffiliationList(self, roomJID, affiliation):
     766        """
     767        Send a request for an affiliation list in a room.
     768        """
     769        def cb(response):
     770            stanza = AdminStanza.fromElement(response)
     771            return stanza.items
     772
     773        request = AdminStanza(recipient=roomJID, stanzaType='get')
     774        request.items = [AdminItem(affiliation=affiliation)]
     775        d = self.request(request)
     776        d.addCallback(cb)
     777        return d
     778
     779
     780    def _getRoleList(self, roomJID, role):
     781        """
     782        Send a request for a role list in a room.
     783        """
     784        def cb(response):
     785            stanza = AdminStanza.fromElement(response)
     786            return stanza.items
     787
     788        request = AdminStanza(recipient=roomJID, stanzaType='get')
     789        request.items = [AdminItem(role=role)]
     790        d = self.request(request)
     791        d.addCallback(cb)
     792        return d
     793
     794
     795    def getMemberList(self, roomJID):
     796        """
     797        Get the member list of a room.
     798
     799        @param roomJID: The bare JID of the room.
     800        @type roomJID: L{jid.JID}
     801        """
     802        return self._getAffiliationList(roomJID, 'member')
     803
     804
     805    def getAdminList(self, roomJID):
     806        """
     807        Get the admin list of a room.
     808
     809        @param roomJID: The bare JID of the room.
     810        @type roomJID: L{jid.JID}
     811        """
     812        return self._getAffiliationList(roomJID, 'admin')
     813
     814
     815    def getBanList(self, roomJID):
     816        """
     817        Get an outcast list from a room.
     818
     819        @param roomJID: The bare JID of the room.
     820        @type roomJID: L{jid.JID}
     821        """
     822        return self._getAffiliationList(roomJID, 'outcast')
     823
     824
     825    def getOwnerList(self, roomJID):
     826        """
     827        Get an owner list from a room.
     828
     829        @param roomJID: The bare JID of the room.
     830        @type roomJID: L{jid.JID}
     831        """
     832        return self._getAffiliationList(roomJID, 'owner')
     833
     834
     835    def getModeratorList(self, roomJID):
     836        """
     837        Get the moderator list of a room.
     838
     839        @param roomJID: The bare JID of the room.
     840        @type roomJID: L{jid.JID}
     841        """
     842        d = self._getRoleList(roomJID, 'moderator')
     843        return d
     844
     845
     846    def _setAffiliation(self, roomJID, entity, affiliation,
     847                              reason=None, sender=None):
     848        """
     849        Send a request to change an entity's affiliation to a MUC room.
     850        """
     851        request = AdminStanza(recipient=roomJID, sender=sender,
     852                               stanzaType='set')
     853        item = AdminItem(entity=entity, affiliation=affiliation, reason=reason)
     854        request.items = [item]
     855        return self.request(request)
     856
     857
     858    def _setRole(self, roomJID, nick, role,
     859                       reason=None, sender=None):
     860        """
     861        Send a request to change an occupant's role in a MUC room.
     862        """
     863        request = AdminStanza(recipient=roomJID, sender=sender,
     864                               stanzaType='set')
     865        item = AdminItem(nick=nick, role=role, reason=reason)
     866        request.items = [item]
     867        return self.request(request)
     868
     869
     870    def modifyAffiliationList(self, roomJID, entities, affiliation,
     871                                    sender=None):
     872        """
     873        Modify an affiliation list.
     874
     875        @param roomJID: The bare JID of the room.
     876        @type roomJID: L{jid.JID}
     877
     878        @param entities: The list of entities to change for a room.
     879        @type entities: L{list} of L{jid.JID}
     880
     881        @param affiliation: The affilation to the entities will acquire.
     882        @type affiliation: C{unicode}
     883
     884        @param sender: The entity sending the request.
     885        @type sender: L{jid.JID}
     886
     887        """
     888        request = AdminStanza(recipient=roomJID, sender=sender,
     889                               stanzaType='set')
     890        request.items = [AdminItem(entity=entity, affiliation=affiliation)
     891                         for entity in entities]
     892
     893        return self.request(request)
     894
     895
     896    def grantVoice(self, roomJID, nick, reason=None, sender=None):
     897        """
     898        Grant voice to an entity.
     899
     900        @param roomJID: The bare JID of the room.
     901        @type roomJID: L{jid.JID}
     902
     903        @param nick: The nick name for the user in this room.
     904        @type nick: C{unicode}
     905
     906        @param reason: The reason for granting voice to the entity.
     907        @type reason: C{unicode}
     908
     909        @param sender: The entity sending the request.
     910        @type sender: L{jid.JID}
     911        """
     912        return self._setRole(roomJID, nick=nick,
     913                             role='participant',
     914                             reason=reason, sender=sender)
     915
     916
     917    def revokeVoice(self, roomJID, nick, reason=None, sender=None):
     918        """
     919        Revoke voice from a participant.
     920
     921        This will disallow the entity to send messages to a moderated room.
     922
     923        @param roomJID: The bare JID of the room.
     924        @type roomJID: L{jid.JID}
     925
     926        @param nick: The nick name for the user in this room.
     927        @type nick: C{unicode}
     928
     929        @param reason: The reason for revoking voice from the entity.
     930        @type reason: C{unicode}
     931
     932        @param sender: The entity sending the request.
     933        @type sender: L{jid.JID}
     934        """
     935        return self._setRole(roomJID, nick=nick, role='visitor',
     936                             reason=reason, sender=sender)
     937
     938
     939    def grantModerator(self, roomJID, nick, reason=None, sender=None):
     940        """
     941        Grant moderator privileges to a MUC room.
     942
     943        @param roomJID: The bare JID of the room.
     944        @type roomJID: L{jid.JID}
     945
     946        @param nick: The nick name for the user in this room.
     947        @type nick: C{unicode}
     948
     949        @param reason: The reason for granting moderation to the entity.
     950        @type reason: C{unicode}
     951
     952        @param sender: The entity sending the request.
     953        @type sender: L{jid.JID}
     954        """
     955        return self._setRole(roomJID, nick=nick, role='moderator',
     956                             reason=reason, sender=sender)
     957
     958
     959    def ban(self, roomJID, entity, reason=None, sender=None):
     960        """
     961        Ban a user from a MUC room.
     962
     963        @param roomJID: The bare JID of the room.
     964        @type roomJID: L{jid.JID}
     965
     966        @param entity: The bare JID of the entity to be banned.
     967        @type entity: L{jid.JID}
     968
     969        @param reason: The reason for banning the entity.
     970        @type reason: C{unicode}
     971
     972        @param sender: The entity sending the request.
     973        @type sender: L{jid.JID}
     974        """
     975        return self._setAffiliation(roomJID, entity, 'outcast',
     976                                    reason=reason, sender=sender)
     977
     978
     979    def kick(self, roomJID, nick, reason=None, sender=None):
     980        """
     981        Kick a user from a MUC room.
     982
     983        @param roomJID: The bare JID of the room.
     984        @type roomJID: L{jid.JID}
     985
     986        @param nick: The occupant to be banned.
     987        @type nick: C{unicode}
     988
     989        @param reason: The reason given for the kick.
     990        @type reason: C{unicode}
     991
     992        @param sender: The entity sending the request.
     993        @type sender: L{jid.JID}
     994        """
     995        return self._setRole(roomJID, nick, 'none',
     996                             reason=reason, sender=sender)
     997
     998
     999    def destroy(self, roomJID, reason=None, alternate=None, password=None):
     1000        """
     1001        Destroy a room.
     1002
     1003        @param roomJID: The JID of the room.
     1004        @type roomJID: L{jid.JID}
     1005
     1006        @param reason: The reason for the destruction of the room.
     1007        @type reason: C{unicode}
     1008
     1009        @param alternate: The JID of the room suggested as an alternate venue.
     1010        @type alternate: L{jid.JID}
     1011
     1012        """
     1013        request = DestructionRequest(recipient=roomJID, reason=reason,
     1014                                     alternate=alternate, password=password)
     1015
     1016        return self.request(request)
     1017
     1018
     1019
     1020class User(object):
     1021    """
     1022    A user/entity in a multi-user chat room.
     1023    """
     1024
     1025    def __init__(self, nick, entity=None):
     1026        self.nick = nick
     1027        self.entity = entity
     1028        self.affiliation = 'none'
     1029        self.role = 'none'
     1030
     1031        self.status = None
     1032        self.show = None
     1033
     1034
     1035
     1036class Room(object):
     1037    """
     1038    A Multi User Chat Room.
     1039
     1040    An in memory object representing a MUC room from the perspective of
     1041    a client.
     1042
     1043    @ivar roomIdentifier: The Room ID of the MUC room.
     1044    @type roomIdentifier: C{unicode}
     1045
     1046    @ivar service: The server where the MUC room is located.
     1047    @type service: C{unicode}
     1048
     1049    @ivar nick: The nick name for the client in this room.
     1050    @type nick: C{unicode}
     1051
     1052    @ivar state: The status code of the room.
     1053    @type state: L{int}
     1054
     1055    @ivar occupantJID: The JID of the occupant in the room. Generated from
     1056        roomIdentifier, service, and nick.
     1057    @type occupantJID: L{jid.JID}
     1058    """
     1059
     1060
     1061    def __init__(self, roomIdentifier, service, nick, state=None):
     1062        """
     1063        Initialize the room.
     1064        """
     1065        self.roomIdentifier = roomIdentifier
     1066        self.service = service
     1067        self.setNick(nick)
     1068        self.state = state
     1069
     1070        self.status = 0
     1071
     1072        self.roster = {}
     1073
     1074
     1075    def setNick(self, nick):
     1076        self.occupantJID = jid.internJID(u"%s@%s/%s" % (self.roomIdentifier,
     1077                                                        self.service,
     1078                                                        nick))
     1079        self.nick = nick
     1080
     1081
     1082    def addUser(self, user):
     1083        """
     1084        Add a user to the room roster.
     1085
     1086        @param user: The user object that is being added to the room.
     1087        @type user: L{User}
     1088        """
     1089        self.roster[user.nick] = user
     1090
     1091
     1092    def inRoster(self, user):
     1093        """
     1094        Check if a user is in the MUC room.
     1095
     1096        @param user: The user object to check.
     1097        @type user: L{User}
     1098        """
     1099
     1100        return user.nick in self.roster
     1101
     1102
     1103    def getUser(self, nick):
     1104        """
     1105        Get a user from the room's roster.
     1106
     1107        @param nick: The nick for the user in the MUC room.
     1108        @type nick: C{unicode}
     1109        """
     1110        return self.roster.get(nick)
     1111
     1112
     1113    def removeUser(self, user):
     1114        """
     1115        Remove a user from the MUC room's roster.
     1116
     1117        @param user: The user object to check.
     1118        @type user: L{User}
     1119        """
     1120        if self.inRoster(user):
     1121            del self.roster[user.nick]
     1122
     1123
     1124
     1125class MUCClient(MUCClientProtocol):
     1126    """
     1127    Multi-User Chat client protocol.
     1128
     1129    This is a subclass of L{XMPPHandler} and implements L{IMUCCLient}.
     1130
     1131    @ivar _rooms: Collection of occupied rooms, keyed by the bare JID of the
     1132                  room. Note that a particular entity can only join a room once
     1133                  at a time.
     1134    @type _rooms: C{dict}
     1135    """
     1136
     1137    implements(IMUCClient)
     1138
     1139    def __init__(self, reactor=None):
     1140        MUCClientProtocol.__init__(self, reactor)
     1141
     1142        self._rooms = {}
    5421143
    5431144
     
    6491250
    6501251
    651     def _onGroupChat(self, element):
     1252    def groupChatReceived(self, message):
    6521253        """
    6531254        A group chat message has been received from a MUC room.
     
    6561257        L{receivedGroupChat}, L{receivedHistory} or L{receivedHistory}.
    6571258        """
    658         message = GroupChat.fromElement(element)
    659 
    6601259        occupantJID = message.sender
    6611260        if not occupantJID:
     
    7121311
    7131312
    714     def initialized(self):
    715         """
    716         Client is initialized and ready!
    717         """
    718         pass
    719 
    720 
    7211313    def userJoinedRoom(self, room, user):
    7221314        """
     
    8071399
    8081400
    809     def sendDeferred(self, stanza):
    810         """
    811         Send presence stanza, adding a deferred with a timeout.
    812 
    813         @param stanza: The presence stanza to send over the wire.
    814         @type stanza: L{generic.Stanza}
    815 
    816         @param timeout: The number of seconds to wait before the deferred is
    817             timed out.
    818         @type timeout: L{int}
    819 
    820         The deferred object L{defer.Deferred} is returned.
    821         """
    822         d = defer.Deferred()
    823 
    824         def onResponse(element):
    825             if element.getAttribute('type') == 'error':
    826                 d.errback(error.exceptionFromStanza(element))
    827             else:
    828                 d.callback(UserPresence.fromElement(element))
    829 
    830         def onTimeout():
    831             d.errback(xmlstream.TimeoutError("Timeout waiting for response."))
    832 
    833         call = self._reactor.callLater(DEFER_TIMEOUT, onTimeout)
    834 
    835         def cancelTimeout(result):
    836             if call.active():
    837                 call.cancel()
    838 
    839             return result
    840 
    841         d.addBoth(cancelTimeout)
    842 
    843         query = "/presence[@from='%s' or (@from='%s' and @type='error')]" % (
    844                 stanza.recipient.full(), stanza.recipient.userhost())
    845         self.xmlstream.addOnetimeObserver(query, onResponse, priority=1)
    846         self.xmlstream.send(stanza.toElement())
    847         return d
    848 
    849 
    850     def configure(self, roomJID, options):
    851         """
    852         Configure a room.
    853 
    854         @param roomJID: The room to configure.
    855         @type roomJID: L{jid.JID}
    856 
    857         @param options: A mapping of field names to values, or C{None} to cancel.
    858         @type options: C{dict}
    859         """
    860         if not options:
    861             options = False
    862         request = ConfigureRequest(recipient=roomJID, options=options)
    863         return self.request(request)
    864 
    865 
    866     def getConfiguration(self, roomJID):
    867         """
    868         Grab the configuration from the room.
    869 
    870         This sends an iq request to the room.
    871 
    872         @param roomJID: The bare JID of the room.
    873         @type roomJID: L{jid.JID}
    874 
    875         @return: A deferred that fires with the room's configuration form as
    876             a L{data_form.Form} or C{None} if there are no configuration
    877             options available.
    878         """
    879         def cb(response):
    880             form = data_form.findForm(response.query, NS_MUC_CONFIG)
    881             return form
    882 
    883         request = ConfigureRequest(recipient=roomJID, options=None)
    884         d = self.request(request)
    885         d.addCallback(cb)
    886         return d
    887 
    888 
    889     def join(self, service, roomIdentifier, nick, history=None):
    890         """
    891         Join a MUC room by sending presence to it.
    892 
    893         @param server: The server where the room is located.
    894         @type server: C{unicode}
    895 
    896         @param room: The room name the entity is joining.
    897         @type room: C{unicode}
    898 
    899         @param nick: The nick name for the entitity joining the room.
    900         @type nick: C{unicode}
    901 
    902         @param history: The maximum number of history stanzas you would like.
    903 
    904         @return: A deferred that fires when the entity is in the room or an
    905                  error has occurred.
    906         """
    907         room = Room(roomIdentifier, service, nick, state='joining')
     1401    def join(self, roomJID, nick, historyOptions=None,
     1402                   password=None):
     1403        room = Room(roomJID.user, roomJID.host, nick, state='joining')
    9081404        self._addRoom(room)
    9091405
    910         presence = BasicPresence(recipient=room.occupantJID)
    911         if history:
    912             presence.history = HistoryOptions(maxstanzas=history)
    913 
    914         d = self.sendDeferred(presence)
     1406        d = MUCClientProtocol.join(self, roomJID, nick, historyOptions,
     1407                                         password)
    9151408        d.addCallback(self._joinedRoom)
    9161409        return d
     
    9621455        room.setNick(nick)
    9631456
    964         # Create presence
    965         presence = BasicPresence(recipient=room.occupantJID)
    966 
    967         d = self.sendDeferred(presence)
     1457        d = MUCClientProtocol.nick(self, roomJID, nick)
    9681458        d.addCallback(self._changed, room.occupantJID)
    9691459        return d
     
    9791469        @type roomJID: L{jid.JID}
    9801470        """
    981         room = self._getRoom(roomJID)
    982 
    983         presence = xmppim.AvailabilityPresence(recipient=room.occupantJID,
    984                                                available=False)
    985 
    986         d = self.sendDeferred(presence)
     1471        d = MUCClientProtocol.leave(self, roomJID)
    9871472        d.addCallback(self._leftRoom)
    9881473        return d
     
    10021487        @type show: C{unicode}
    10031488
    1004         @param show: The current status of the entity.
    1005         @type show: C{unicode}
     1489        @param status: The current status of the entity.
     1490        @type status: C{unicode}
    10061491        """
    10071492        room = self._getRoom(roomJID)
    1008 
    1009         presence = BasicPresence(recipient=room.occupantJID,
    1010                                  show=show, status=status)
    1011 
    1012         d = self.sendDeferred(presence)
     1493        d = MUCClientProtocol.status(self, roomJID, show, status)
    10131494        d.addCallback(self._changed, room.occupantJID)
    10141495        return d
    10151496
    10161497
    1017     def _sendMessage(self, message, children=None):
    1018         """
    1019         Send a message.
    1020         """
    1021         element = message.toElement()
    1022         if children:
    1023             for child in children:
    1024                 element.addChild(child)
    1025 
    1026         self.xmlstream.send(element)
    1027 
    1028 
    1029     def groupChat(self, roomJID, body, children=None):
    1030         """
    1031         Send a groupchat message.
    1032         """
    1033         message = GroupChat(recipient=roomJID, body=body)
    1034         self._sendMessage(message, children=children)
    1035 
    1036 
    1037     def chat(self, occupantJID, body, children=None):
    1038         """
    1039         Send a private chat message to a user in a MUC room.
    1040 
    1041         See: http://xmpp.org/extensions/xep-0045.html#privatemessage
    1042 
    1043         @param occupantJID: The Room JID of the other user.
    1044         @type occupantJID: L{jid.JID}
    1045         """
    1046         message = PrivateChat(recipient=occupantJID, body=body)
    1047         self._sendMessage(message, children=children)
    1048 
    1049 
    1050     def invite(self, roomJID, invitee, reason=None):
    1051         """
    1052         Invite a xmpp entity to a MUC room.
    1053 
    1054         See: http://xmpp.org/extensions/xep-0045.html#invite
    1055 
    1056         @param roomJID: The bare JID of the room.
    1057         @type roomJID: L{jid.JID}
    1058 
    1059         @param invitee: The entity that is being invited.
    1060         @type invitee: L{jid.JID}
    1061 
    1062         @param reason: The reason for the invite.
    1063         @type reason: C{unicode}
    1064         """
    1065         message = InviteMessage(recipient=roomJID, invitee=invitee,
    1066                                 reason=reason)
    1067         self._sendMessage(message)
    1068 
    1069 
    1070     def password(self, roomJID, password):
    1071         """
    1072         Send a password to a room so the entity can join.
    1073 
    1074         See: http://xmpp.org/extensions/xep-0045.html#enter-pw
    1075 
    1076         @param roomJID: The bare JID of the room.
    1077         @type roomJID: L{jid.JID}
    1078 
    1079         @param password: The MUC room password.
    1080         @type password: C{unicode}
    1081         """
    1082         presence = BasicPresence(roomJID)
    1083         presence.password = password
    1084 
    1085         self.xmlstream.send(presence.toElement())
    1086 
    1087 
    1088     def register(self, roomJID, options):
    1089         """
    1090         Send a request to register for a room.
    1091 
    1092         @param roomJID: The bare JID of the room.
    1093         @type roomJID: L{jid.JID}
    1094 
    1095         @param options: A mapping of field names to values, or C{None} to
    1096             cancel.
    1097         @type options: C{dict}
    1098         """
    1099         request = RegisterRequest(recipient=roomJID, options=options)
    1100         return self.request(request)
    1101 
    1102 
    1103     def _getAffiliationList(self, roomJID, affiliation):
    1104         """
    1105         Send a request for an affiliation list in a room.
    1106         """
    1107         def cb(response):
    1108             stanza = AdminStanza.fromElement(response)
    1109             return stanza.items
    1110 
    1111         request = AdminStanza(recipient=roomJID, stanzaType='get')
    1112         request.items = [AdminItem(affiliation=affiliation)]
    1113         d = self.request(request)
    1114         d.addCallback(cb)
    1115         return d
    1116 
    1117 
    1118     def _getRoleList(self, roomJID, role):
    1119         """
    1120         Send a request for a role list in a room.
    1121         """
    1122         def cb(response):
    1123             stanza = AdminStanza.fromElement(response)
    1124             return stanza.items
    1125 
    1126         request = AdminStanza(recipient=roomJID, stanzaType='get')
    1127         request.items = [AdminItem(role=role)]
    1128         d = self.request(request)
    1129         d.addCallback(cb)
    1130         return d
    1131 
    1132 
    1133     def getMemberList(self, roomJID):
    1134         """
    1135         Get the member list of a room.
    1136 
    1137         @param roomJID: The bare JID of the room.
    1138         @type roomJID: L{jid.JID}
    1139         """
    1140         return self._getAffiliationList(roomJID, 'member')
    1141 
    1142 
    1143     def getAdminList(self, roomJID):
    1144         """
    1145         Get the admin list of a room.
    1146 
    1147         @param roomJID: The bare JID of the room.
    1148         @type roomJID: L{jid.JID}
    1149         """
    1150         return self._getAffiliationList(roomJID, 'admin')
    1151 
    1152 
    1153     def getBanList(self, roomJID):
    1154         """
    1155         Get an outcast list from a room.
    1156 
    1157         @param roomJID: The bare JID of the room.
    1158         @type roomJID: L{jid.JID}
    1159         """
    1160         return self._getAffiliationList(roomJID, 'outcast')
    1161 
    1162 
    1163     def getOwnerList(self, roomJID):
    1164         """
    1165         Get an owner list from a room.
    1166 
    1167         @param roomJID: The bare JID of the room.
    1168         @type roomJID: L{jid.JID}
    1169         """
    1170         return self._getAffiliationList(roomJID, 'owner')
    1171 
    1172 
    1173     def getModeratorList(self, roomJID):
    1174         """
    1175         Get the moderator list of a room.
    1176 
    1177         @param roomJID: The bare JID of the room.
    1178         @type roomJID: L{jid.JID}
    1179         """
    1180         d = self._getRoleList(roomJID, 'moderator')
    1181         return d
    1182 
    1183 
    1184     def getRegisterForm(self, roomJID):
    1185         """
    1186         Grab the registration form for a MUC room.
    1187 
    1188         @param room: The room jabber/xmpp entity id for the requested
    1189             registration form.
    1190         @type room: L{jid.JID}
    1191         """
    1192         iq = RegisterRequest(self.xmlstream)
    1193         iq['to'] = roomJID.userhost()
    1194         return iq.send()
    1195 
    1196 
    11971498    def destroy(self, roomJID, reason=None, alternate=None, password=None):
    11981499        """
     
    12121513            self._removeRoom(roomJID)
    12131514
    1214         request = DestructionRequest(recipient=roomJID, reason=reason,
    1215                                      alternate=alternate, password=password)
    1216 
    1217         d = self.request(request)
     1515        d = MUCClientProtocol.destroy(self, roomJID, reason, alternate)
    12181516        d.addCallback(destroyed)
    12191517        return d
    1220 
    1221 
    1222     def subject(self, roomJID, subject):
    1223         """
    1224         Change the subject of a MUC room.
    1225 
    1226         See: http://xmpp.org/extensions/xep-0045.html#subject-mod
    1227 
    1228         @param roomJID: The bare JID of the room.
    1229         @type roomJID: L{jid.JID}
    1230 
    1231         @param subject: The subject you want to set.
    1232         @type subject: C{unicode}
    1233         """
    1234         msg = GroupChat(roomJID.userhostJID(), subject=subject)
    1235         self.xmlstream.send(msg.toElement())
    1236 
    1237 
    1238     def voice(self, roomJID):
    1239         """
    1240         Request voice for a moderated room.
    1241 
    1242         @param roomJID: The room jabber/xmpp entity id.
    1243         @type roomJID: L{jid.JID}
    1244         """
    1245         message = VoiceRequest(recipient=roomJID)
    1246         self.xmlstream.send(message.toElement())
    1247 
    1248 
    1249     def history(self, roomJID, messages):
    1250         """
    1251         Send history to create a MUC based on a one on one chat.
    1252 
    1253         See: http://xmpp.org/extensions/xep-0045.html#continue
    1254 
    1255         @param roomJID: The room jabber/xmpp entity id.
    1256         @type roomJID: L{jid.JID}
    1257 
    1258         @param messages: The history to send to the room as an ordered list of
    1259                          message, represented by a dictionary with the keys
    1260                          C{'stanza'}, holding the original stanza a
    1261                          L{domish.Element}, and C{'timestamp'} with the
    1262                          timestamp.
    1263         @type messages: L{list} of L{domish.Element}
    1264         """
    1265 
    1266         for message in messages:
    1267             stanza = message['stanza']
    1268             stanza['type'] = 'groupchat'
    1269 
    1270             delay = Delay(stamp=message['timestamp'])
    1271 
    1272             sender = stanza.getAttribute('from')
    1273             if sender is not None:
    1274                 delay.sender = jid.JID(sender)
    1275 
    1276             stanza.addChild(delay.toElement())
    1277 
    1278             stanza['to'] = roomJID.userhost()
    1279             if stanza.hasAttribute('from'):
    1280                 del stanza['from']
    1281 
    1282             self.xmlstream.send(stanza)
    1283 
    1284 
    1285     def _setAffiliation(self, roomJID, entity, affiliation,
    1286                               reason=None, sender=None):
    1287         """
    1288         Send a request to change an entity's affiliation to a MUC room.
    1289         """
    1290         request = AdminStanza(recipient=roomJID, sender=sender,
    1291                                stanzaType='set')
    1292         item = AdminItem(entity=entity, affiliation=affiliation, reason=reason)
    1293         request.items = [item]
    1294         return self.request(request)
    1295 
    1296 
    1297     def _setRole(self, roomJID, nick, role,
    1298                        reason=None, sender=None):
    1299         """
    1300         Send a request to change an occupant's role in a MUC room.
    1301         """
    1302         request = AdminStanza(recipient=roomJID, sender=sender,
    1303                                stanzaType='set')
    1304         item = AdminItem(nick=nick, role=role, reason=reason)
    1305         request.items = [item]
    1306         return self.request(request)
    1307 
    1308 
    1309     def modifyAffiliationList(self, roomJID, entities, affiliation,
    1310                                     sender=None):
    1311         """
    1312         Modify an affiliation list.
    1313 
    1314         @param roomJID: The bare JID of the room.
    1315         @type roomJID: L{jid.JID}
    1316 
    1317         @param entities: The list of entities to change for a room.
    1318         @type entities: L{list} of L{jid.JID}
    1319 
    1320         @param affiliation: The affilation to the entities will acquire.
    1321         @type affiliation: C{unicode}
    1322 
    1323         @param sender: The entity sending the request.
    1324         @type sender: L{jid.JID}
    1325 
    1326         """
    1327         request = AdminStanza(recipient=roomJID, sender=sender,
    1328                                stanzaType='set')
    1329         request.items = [AdminItem(entity=entity, affiliation=affiliation)
    1330                          for entity in entities]
    1331 
    1332         return self.request(request)
    1333 
    1334 
    1335     def grantVoice(self, roomJID, nick, reason=None, sender=None):
    1336         """
    1337         Grant voice to an entity.
    1338 
    1339         @param roomJID: The bare JID of the room.
    1340         @type roomJID: L{jid.JID}
    1341 
    1342         @param nick: The nick name for the user in this room.
    1343         @type nick: C{unicode}
    1344 
    1345         @param reason: The reason for granting voice to the entity.
    1346         @type reason: C{unicode}
    1347 
    1348         @param sender: The entity sending the request.
    1349         @type sender: L{jid.JID}
    1350         """
    1351         return self._setRole(roomJID, nick=nick,
    1352                              role='participant',
    1353                              reason=reason, sender=sender)
    1354 
    1355 
    1356     def revokeVoice(self, roomJID, nick, reason=None, sender=None):
    1357         """
    1358         Revoke voice from a participant.
    1359 
    1360         This will disallow the entity to send messages to a moderated room.
    1361 
    1362         @param roomJID: The bare JID of the room.
    1363         @type roomJID: L{jid.JID}
    1364 
    1365         @param nick: The nick name for the user in this room.
    1366         @type nick: C{unicode}
    1367 
    1368         @param reason: The reason for revoking voice from the entity.
    1369         @type reason: C{unicode}
    1370 
    1371         @param sender: The entity sending the request.
    1372         @type sender: L{jid.JID}
    1373         """
    1374         return self._setRole(roomJID, nick=nick, role='visitor',
    1375                              reason=reason, sender=sender)
    1376 
    1377 
    1378     def grantModerator(self, roomJID, nick, reason=None, sender=None):
    1379         """
    1380         Grant moderator privileges to a MUC room.
    1381 
    1382         @param roomJID: The bare JID of the room.
    1383         @type roomJID: L{jid.JID}
    1384 
    1385         @param nick: The nick name for the user in this room.
    1386         @type nick: C{unicode}
    1387 
    1388         @param reason: The reason for granting moderation to the entity.
    1389         @type reason: C{unicode}
    1390 
    1391         @param sender: The entity sending the request.
    1392         @type sender: L{jid.JID}
    1393         """
    1394         return self._setRole(roomJID, nick=nick, role='moderator',
    1395                              reason=reason, sender=sender)
    1396 
    1397 
    1398     def ban(self, roomJID, entity, reason=None, sender=None):
    1399         """
    1400         Ban a user from a MUC room.
    1401 
    1402         @param roomJID: The bare JID of the room.
    1403         @type roomJID: L{jid.JID}
    1404 
    1405         @param entity: The bare JID of the entity to be banned.
    1406         @type entity: L{jid.JID}
    1407 
    1408         @param reason: The reason for banning the entity.
    1409         @type reason: C{unicode}
    1410 
    1411         @param sender: The entity sending the request.
    1412         @type sender: L{jid.JID}
    1413         """
    1414         return self._setAffiliation(roomJID, entity, 'outcast',
    1415                                     reason=reason, sender=sender)
    1416 
    1417 
    1418     def kick(self, roomJID, nick, reason=None, sender=None):
    1419         """
    1420         Kick a user from a MUC room.
    1421 
    1422         @param roomJID: The bare JID of the room.
    1423         @type roomJID: L{jid.JID}
    1424 
    1425         @param nick: The occupant to be banned.
    1426         @type nick: C{unicode}
    1427 
    1428         @param reason: The reason given for the kick.
    1429         @type reason: C{unicode}
    1430 
    1431         @param sender: The entity sending the request.
    1432         @type sender: L{jid.JID}
    1433         """
    1434         return self._setRole(roomJID, nick, 'none',
    1435                              reason=reason, sender=sender)
  • wokkel/test/test_muc.py

    r151 r152  
    1818from twisted.words.protocols.jabber.xmlstream import TimeoutError, toResponse
    1919
    20 from wokkel import data_form, iwokkel, muc
     20from wokkel import data_form, delay, iwokkel, muc
    2121from wokkel.generic import parseXml
    2222from wokkel.test.helpers import TestableStreamManager
     
    4343
    4444
    45 class MUCClientTest(unittest.TestCase):
    46     timeout = 2
     45class GroupChatTest(unittest.TestCase):
     46    """
     47    Tests for {muc.GroupChat}.
     48    """
     49
     50
     51    def test_toElementDelay(self):
     52        """
     53        If the delay attribute is set, toElement has it rendered.
     54        """
     55        message = muc.GroupChat()
     56        message.delay = delay.Delay(stamp=datetime(2002, 10, 13, 23, 58, 37,
     57                                                   tzinfo=tzutc()))
     58
     59        element = message.toElement()
     60
     61        query = "/message/delay[@xmlns='%s']" % (delay.NS_DELAY,)
     62        nodes = xpath.queryForNodes(query, element)
     63        self.assertNotIdentical(None, nodes, "Missing delay element")
     64
     65
     66    def test_toElementDelayLegacy(self):
     67        """
     68        If legacy delay is requested, the legacy format is rendered.
     69        """
     70        message = muc.GroupChat()
     71        message.delay = delay.Delay(stamp=datetime(2002, 10, 13, 23, 58, 37,
     72                                                   tzinfo=tzutc()))
     73
     74        element = message.toElement(legacyDelay=True)
     75
     76        query = "/message/x[@xmlns='%s']" % (delay.NS_JABBER_DELAY,)
     77        nodes = xpath.queryForNodes(query, element)
     78        self.assertNotIdentical(None, nodes, "Missing legacy delay element")
     79
     80
     81
     82class HistoryOptionsTest(unittest.TestCase):
     83    """
     84    Tests for L{muc.HistoryOptionsTest}.
     85    """
     86
     87    def test_toElement(self):
     88        """
     89        toElement renders the history element in the right namespace.
     90        """
     91        history = muc.HistoryOptions()
     92
     93        element = history.toElement()
     94
     95        self.assertEqual(muc.NS_MUC, element.uri)
     96        self.assertEqual('history', element.name)
     97
     98
     99    def test_toElementMaxStanzas(self):
     100        """
     101        If C{maxStanzas} is set, the element has the attribute C{'maxstanzas'}.
     102        """
     103        history = muc.HistoryOptions(maxStanzas=10)
     104
     105        element = history.toElement()
     106
     107        self.assertEqual(u'10', element.getAttribute('maxstanzas'))
     108
     109
     110    def test_toElementSince(self):
     111        """
     112        If C{since} is set, the attribute C{'since'} has a rendered timestamp.
     113        """
     114        history = muc.HistoryOptions(since=datetime(2002, 10, 13, 23, 58, 37,
     115                                                   tzinfo=tzutc()))
     116
     117        element = history.toElement()
     118
     119        self.assertEqual(u'2002-10-13T23:58:37Z',
     120                         element.getAttribute('since'))
     121
     122
     123
     124class MUCClientProtocolTest(unittest.TestCase):
     125    """
     126    Tests for L{muc.MUCClientProtocol}.
     127    """
    47128
    48129    def setUp(self):
     
    50131        self.sessionManager = TestableStreamManager(reactor=self.clock)
    51132        self.stub = self.sessionManager.stub
    52         self.protocol = muc.MUCClient(reactor=self.clock)
     133        self.protocol = muc.MUCClientProtocol(reactor=self.clock)
    53134        self.protocol.setHandlerParent(self.sessionManager)
    54135
     
    64145
    65146
    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.
     147    def test_initNoReactor(self):
     148        """
     149        If no reactor is passed, the default reactor is used.
     150        """
     151        protocol = muc.MUCClientProtocol()
     152        from twisted.internet import reactor
     153        self.assertEqual(reactor, protocol._reactor)
     154
     155
     156    def test_groupChatReceived(self):
     157        """
     158        Messages of type groupchat are parsed and passed to L{groupChatReceived}.
    140159        """
    141160        xml = u"""
     
    145164        """ % (self.occupantJID)
    146165
    147         self._createRoom()
    148 
    149         def receivedGroupChat(room, user, message):
     166        def groupChatReceived(message):
    150167            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}.
     168            self.assertEquals(self.roomIdentifier, message.sender.user,
     169                              'Wrong room identifier')
     170
     171        d, self.protocol.groupChatReceived = calledAsync(groupChatReceived)
     172        self.stub.send(parseXml(xml))
     173        return d
     174
     175
     176    def test_groupChatReceivedNotOverridden(self):
     177        """
     178        If L{groupChatReceived} has not been overridden, no errors should occur.
    162179        """
    163180        xml = u"""
     
    165182              <body>test</body>
    166183            </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
     184        """ % (self.occupantJID)
     185
     186        self.stub.send(parseXml(xml))
    177187
    178188
    179189    def test_join(self):
    180190        """
    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)
     191        Joining a room waits for confirmation, deferred fires user presence.
     192        """
     193
     194        def cb(presence):
     195            self.assertEquals(self.occupantJID, presence.sender)
     196
     197        # Join the room
     198        d = self.protocol.join(self.roomJID, self.nick)
    188199        d.addCallback(cb)
    189200
    190201        element = self.stub.output[-1]
     202
    191203        self.assertEquals('presence', element.name, "Need to be presence")
    192204        self.assertNotIdentical(None, element.x, 'No muc x element')
     
    206218    def test_joinHistory(self):
    207219        """
    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)
     220        Passing a history parameter sends a 'maxStanzas' history limit.
     221        """
     222
     223        historyOptions = muc.HistoryOptions(maxStanzas=10)
     224        d = self.protocol.join(self.roomJID, self.nick,
     225                               historyOptions)
    217226
    218227        element = self.stub.output[-1]
     
    244253                              'Wrong muc condition')
    245254
    246         d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
     255        d = self.protocol.join(self.roomJID, self.nick)
    247256        self.assertFailure(d, StanzaError)
    248257        d.addCallback(cb)
     
    268277        """
    269278
    270         d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
     279        d = self.protocol.join(self.roomJID, self.nick)
    271280        self.assertFailure(d, StanzaError)
    272281
     
    292301                              'Wrong muc condition')
    293302
    294         d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
     303        d = self.protocol.join(self.roomJID, self.nick)
    295304        self.assertFailure(d, StanzaError)
    296305        d.addCallback(cb)
     
    313322        """
    314323
    315         d = self.protocol.join(self.service, self.roomIdentifier, self.nick)
     324        d = self.protocol.join(self.roomJID, self.nick)
    316325        self.assertFailure(d, TimeoutError)
    317326        self.clock.advance(muc.DEFER_TIMEOUT)
     
    319328
    320329
    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.roomJID, 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.roomJID, 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                     "[@role='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):
     330    def test_joinPassword(self):
    426331        """
    427332        Sending a password via presence to a password protected room.
    428333        """
    429334
    430         self.protocol.password(self.occupantJID, 'secret')
     335        self.protocol.join(self.roomJID, self.nick, password='secret')
    431336
    432337        element = self.stub.output[-1]
     
    439344
    440345
    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):
     346    def test_nick(self):
     347        """
     348        Send a nick change to the server.
     349        """
     350        newNick = 'newNick'
     351
     352        def cb(presence):
     353            self.assertEquals(JID(tuple=(self.roomIdentifier,
     354                                         self.service,
     355                                         newNick)),
     356                              presence.sender)
     357
     358        d = self.protocol.nick(self.roomJID, newNick)
     359        d.addCallback(cb)
     360
     361        element = self.stub.output[-1]
     362        self.assertEquals('presence', element.name, "Need to be presence")
     363        self.assertNotIdentical(None, element.x, 'No muc x element')
     364
     365        # send back user presence, nick changed
     366        xml = u"""
     367            <presence from='%s/%s'>
     368              <x xmlns='http://jabber.org/protocol/muc#user'>
     369                <item affiliation='member' role='participant'/>
     370              </x>
     371            </presence>
     372        """ % (self.roomJID, newNick)
     373        self.stub.send(parseXml(xml))
     374        return d
     375
     376
     377    def test_nickConflict(self):
     378        """
     379        If the server finds the new nick in conflict, the errback is called.
     380        """
     381        newNick = 'newNick'
     382
     383        d = self.protocol.nick(self.roomJID, newNick)
     384        self.assertFailure(d, StanzaError)
     385
     386        element = self.stub.output[-1]
     387        self.assertEquals('presence', element.name, "Need to be presence")
     388        self.assertNotIdentical(None, element.x, 'No muc x element')
     389
     390        # send back user presence, nick changed
     391        xml = u"""
     392            <presence from='%s/%s' type='error'>
     393                <x xmlns='http://jabber.org/protocol/muc'/>
     394                <error type='cancel'>
     395                  <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
     396                </error>
     397            </presence>
     398        """ % (self.roomJID, newNick)
     399        self.stub.send(parseXml(xml))
     400        return d
     401
     402
     403    def test_status(self):
     404        """
     405        Change status
     406        """
     407        def joined(_):
     408            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
     409            d.addCallback(statusChanged)
     410            return d
     411
     412        def statusChanged(presence):
     413            self.assertEqual(self.occupantJID, presence.sender)
     414
     415        # Join the room
     416        d = self.protocol.join(self.roomJID, self.nick)
     417        d.addCallback(joined)
     418
     419        # Receive presence back from the room: joined.
     420        xml = u"""
     421            <presence to='%s' from='%s'/>
     422        """ % (self.userJID, self.occupantJID)
     423        self.stub.send(parseXml(xml))
     424
     425        # The presence for the status change should have been sent now.
     426        element = self.stub.output[-1]
     427
     428        self.assertEquals('presence', element.name, "Need to be presence")
     429        self.assertTrue(getattr(element, 'x', None), 'No muc x element')
     430
     431        # send back user presence, status changed
     432        xml = u"""
     433            <presence from='%s'>
     434              <x xmlns='http://jabber.org/protocol/muc#user'>
     435                <item affiliation='member' role='participant'/>
     436              </x>
     437              <show>xa</show>
     438              <status>testing MUC</status>
     439            </presence>
     440        """ % self.occupantJID
     441        self.stub.send(parseXml(xml))
     442
     443        return d
     444
     445
     446    def test_leave(self):
     447        """
     448        Client leaves a room
     449        """
     450        def joined(_):
     451            return self.protocol.leave(self.roomJID)
     452
     453        # Join the room
     454        d = self.protocol.join(self.roomJID, self.nick)
     455        d.addCallback(joined)
     456
     457        # Receive presence back from the room: joined.
     458        xml = u"""
     459            <presence to='%s' from='%s'/>
     460        """ % (self.userJID, self.occupantJID)
     461        self.stub.send(parseXml(xml))
     462
     463        # The presence for leaving the room should have been sent now.
     464        element = self.stub.output[-1]
     465
     466        self.assertEquals('unavailable', element['type'],
     467                          'Unavailable is not being sent')
     468
     469        # Receive presence back from the room: left.
     470        xml = u"""
     471            <presence to='%s' from='%s' type='unavailable'/>
     472        """ % (self.userJID, self.occupantJID)
     473        self.stub.send(parseXml(xml))
     474
     475        return d
     476
     477
     478    def test_groupChat(self):
     479        """
     480        Send private messages to muc entities.
     481        """
     482        self.protocol.groupChat(self.roomJID, u'This is a test')
     483
     484        message = self.stub.output[-1]
     485
     486        self.assertEquals('message', message.name)
     487        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
     488        self.assertEquals('groupchat', message.getAttribute('type'))
     489        self.assertEquals(u'This is a test', unicode(message.body))
     490
     491
     492    def test_chat(self):
     493        """
     494        Send private messages to muc entities.
     495        """
     496        otherOccupantJID = JID(self.occupantJID.userhost()+'/OtherNick')
     497
     498        self.protocol.chat(otherOccupantJID, u'This is a test')
     499
     500        message = self.stub.output[-1]
     501
     502        self.assertEquals('message', message.name)
     503        self.assertEquals(otherOccupantJID.full(), message.getAttribute('to'))
     504        self.assertEquals('chat', message.getAttribute('type'))
     505        self.assertEquals(u'This is a test', unicode(message.body))
     506
     507
     508    def test_subject(self):
     509        """
     510        Change subject of the room.
     511        """
     512        self.protocol.subject(self.roomJID, u'This is a test')
     513
     514        message = self.stub.output[-1]
     515
     516        self.assertEquals('message', message.name)
     517        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
     518        self.assertEquals('groupchat', message.getAttribute('type'))
     519        self.assertEquals(u'This is a test', unicode(message.subject))
     520
     521
     522    def test_invite(self):
     523        """
     524        Invite a user to a room
     525        """
     526        invitee = JID('other@example.org')
     527
     528        self.protocol.invite(self.roomJID, invitee, u'This is a test')
     529
     530        message = self.stub.output[-1]
     531
     532        self.assertEquals('message', message.name)
     533        self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
     534        self.assertEquals(muc.NS_MUC_USER, message.x.uri)
     535        self.assertEquals(muc.NS_MUC_USER, message.x.invite.uri)
     536        self.assertEquals(invitee.full(), message.x.invite.getAttribute('to'))
     537        self.assertEquals(muc.NS_MUC_USER, message.x.invite.reason.uri)
     538        self.assertEquals(u'This is a test', unicode(message.x.invite.reason))
     539
     540
     541    def test_getRegisterForm(self):
     542        """
     543        The response of a register form request should extract the form.
     544        """
     545
     546        def cb(form):
     547            self.assertEquals('form', form.formType)
     548
     549        d = self.protocol.getRegisterForm(self.roomJID)
     550        d.addCallback(cb)
     551
     552        iq = self.stub.output[-1]
     553
     554        query = "/iq/query[@xmlns='%s']" % (muc.NS_REGISTER)
     555        nodes = xpath.queryForNodes(query, iq)
     556        self.assertNotIdentical(None, nodes, 'Missing query element')
     557
     558        self.assertRaises(StopIteration, nodes[0].elements().next)
     559
     560        xml = u"""
     561            <iq from='%s' id='%s' to='%s' type='result'>
     562              <query xmlns='jabber:iq:register'>
     563                <x xmlns='jabber:x:data' type='form'>
     564                  <field type='hidden'
     565                         var='FORM_TYPE'>
     566                    <value>http://jabber.org/protocol/muc#register</value>
     567                  </field>
     568                  <field label='Desired Nickname'
     569                         type='text-single'
     570                         var='muc#register_roomnick'>
     571                    <required/>
     572                  </field>
     573                </x>
     574              </query>
     575            </iq>
     576        """ % (self.roomJID, iq['id'], self.userJID)
     577        self.stub.send(parseXml(xml))
     578
     579        return d
     580
     581
     582    def test_register(self):
     583        """
     584        Client registering with a room.
     585
     586        http://xmpp.org/extensions/xep-0045.html#register
     587        """
     588
     589        def cb(iq):
     590            # check for a result
     591            self.assertEquals('result', iq['type'], 'We did not get a result')
     592
     593        d = self.protocol.register(self.roomJID,
     594                                   {'muc#register_roomnick': 'thirdwitch'})
     595        d.addCallback(cb)
     596
     597        iq = self.stub.output[-1]
     598
     599        query = "/iq/query[@xmlns='%s']" % muc.NS_REGISTER
     600        nodes = xpath.queryForNodes(query, iq)
     601        self.assertNotIdentical(None, nodes, 'Invalid registration request')
     602
     603        form = data_form.findForm(nodes[0], muc.NS_MUC_REGISTER)
     604        self.assertNotIdentical(None, form, 'Missing registration form')
     605        self.assertEquals('submit', form.formType)
     606        self.assertIn('muc#register_roomnick', form.fields)
     607
     608
     609        response = toResponse(iq, 'result')
     610        self.stub.send(response)
     611        return d
     612
     613
     614    def test_voice(self):
     615        """
     616        Client requesting voice for a room.
     617        """
     618        self.protocol.voice(self.occupantJID)
     619
     620        m = self.stub.output[-1]
     621
     622        query = ("/message/x[@type='submit']/field/value"
     623                    "[text()='%s']") % muc.NS_MUC_REQUEST
     624        self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
     625
     626
     627    def test_history(self):
    468628        """
    469629        Converting a one to one chat to a multi-user chat.
     
    501661            self.assertTrue(xpath.matches("/message/delay", element),
    502662                            '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         def cb(iq):
    562             # check for a result
    563             self.assertEquals('result', iq['type'], 'We did not get a result')
    564 
    565         d = self.protocol.register(self.roomJID,
    566                                    {'muc#register_roomnick': 'thirdwitch'})
    567         d.addCallback(cb)
    568 
    569         iq = self.stub.output[-1]
    570 
    571         query = "/iq/query[@xmlns='%s']" % muc.NS_REGISTER
    572         nodes = xpath.queryForNodes(query, iq)
    573         self.assertNotIdentical(None, nodes, 'Invalid registration request')
    574 
    575         form = data_form.findForm(nodes[0], muc.NS_MUC_REGISTER)
    576         self.assertNotIdentical(None, form, 'Missing registration form')
    577         self.assertEquals('submit', form.formType)
    578         self.assertIn('muc#register_roomnick', form.fields)
    579 
    580 
    581         response = toResponse(iq, 'result')
    582         self.stub.send(response)
    583         return d
    584 
    585 
    586     def test_voice(self):
    587         """
    588         Client requesting voice for a room.
    589         """
    590         self.protocol.voice(self.occupantJID)
    591 
    592         m = self.stub.output[-1]
    593 
    594         query = ("/message/x[@type='submit']/field/value"
    595                     "[text()='%s']") % muc.NS_MUC_REQUEST
    596         self.assertTrue(xpath.matches(query, m), 'Invalid voice message stanza')
    597 
    598 
    599     def test_configure(self):
    600         """
    601         Default configure and changing the room name.
    602         """
    603 
    604         def cb(iq):
    605             self.assertEquals('result', iq['type'], 'Not a result')
    606 
    607         values = {'muc#roomconfig_roomname': self.roomIdentifier}
    608 
    609         d = self.protocol.configure(self.roomJID, values)
    610         d.addCallback(cb)
    611 
    612         iq = self.stub.output[-1]
    613 
    614         self.assertEquals('set', iq.getAttribute('type'))
    615         self.assertEquals(self.roomJID.full(), iq.getAttribute('to'))
    616 
    617         query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
    618         nodes = xpath.queryForNodes(query, iq)
    619         self.assertNotIdentical(None, nodes, 'Bad configure request')
    620 
    621         form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
    622         self.assertNotIdentical(None, form, 'Missing configuration form')
    623         self.assertEquals('submit', form.formType)
    624 
    625         response = toResponse(iq, 'result')
    626         self.stub.send(response)
    627         return d
    628 
    629 
    630     def test_configureCancel(self):
    631         """
    632         Cancelling room configuration should send a cancel form.
    633         """
    634 
    635         d = self.protocol.configure(self.roomJID, None)
    636 
    637         iq = self.stub.output[-1]
    638 
    639         query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
    640         nodes = xpath.queryForNodes(query, iq)
    641 
    642         form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
    643         self.assertNotIdentical(None, form, 'Missing configuration form')
    644         self.assertEquals('cancel', form.formType)
    645 
    646         response = toResponse(iq, 'result')
    647         self.stub.send(response)
    648         return d
    649663
    650664
     
    711725
    712726
    713     def test_destroy(self):
    714         """
    715         Destroy a room.
    716         """
    717         d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
    718                                   alternate=JID('other@%s' % self.service),
    719                                   password='secret')
    720 
    721         iq = self.stub.output[-1]
    722 
    723         query = ("/iq/query[@xmlns='%s']/destroy[@xmlns='%s']" %
    724                  (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER))
    725 
     727    def test_configure(self):
     728        """
     729        Default configure and changing the room name.
     730        """
     731
     732        def cb(iq):
     733            self.assertEquals('result', iq['type'], 'Not a result')
     734
     735        values = {'muc#roomconfig_roomname': self.roomIdentifier}
     736
     737        d = self.protocol.configure(self.roomJID, values)
     738        d.addCallback(cb)
     739
     740        iq = self.stub.output[-1]
     741
     742        self.assertEquals('set', iq.getAttribute('type'))
     743        self.assertEquals(self.roomJID.full(), iq.getAttribute('to'))
     744
     745        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
    726746        nodes = xpath.queryForNodes(query, iq)
    727747        self.assertNotIdentical(None, nodes, 'Bad configure request')
    728         destroy = nodes[0]
    729         self.assertEquals('Time to leave', unicode(destroy.reason))
    730 
    731         response = toResponse(iq, 'result')
    732         self.stub.send(response)
    733         return d
    734 
    735 
    736     def test_subject(self):
    737         """
    738         Change subject of the room.
    739         """
    740         self.protocol.subject(self.roomJID, u'This is a test')
    741 
    742         message = self.stub.output[-1]
    743 
    744         self.assertEquals('message', message.name)
    745         self.assertEquals(self.roomJID.full(), message.getAttribute('to'))
    746         self.assertEquals('groupchat', message.getAttribute('type'))
    747         self.assertEquals(u'This is a test', unicode(message.subject))
    748 
    749 
    750     def test_nick(self):
    751         """
    752         Send a nick change to the server.
    753         """
    754         newNick = 'newNick'
    755 
    756         self._createRoom()
    757 
    758         def cb(room):
    759             self.assertEquals(self.roomIdentifier, room.roomIdentifier)
    760             self.assertEquals(newNick, room.nick)
    761 
    762         d = self.protocol.nick(self.roomJID, newNick)
    763         d.addCallback(cb)
    764 
    765         element = self.stub.output[-1]
    766         self.assertEquals('presence', element.name, "Need to be presence")
    767         self.assertNotIdentical(None, element.x, 'No muc x element')
    768 
    769         # send back user presence, nick changed
    770         xml = u"""
    771             <presence from='%s/%s'>
    772               <x xmlns='http://jabber.org/protocol/muc#user'>
    773                 <item affiliation='member' role='participant'/>
    774               </x>
    775             </presence>
    776         """ % (self.roomJID, newNick)
    777         self.stub.send(parseXml(xml))
    778         return d
    779 
    780 
    781     def test_nickConflict(self):
    782         """
    783         If the server finds the new nick in conflict, the errback is called.
    784         """
    785         newNick = 'newNick'
    786 
    787         self._createRoom()
    788 
    789         d = self.protocol.nick(self.roomJID, newNick)
    790         self.assertFailure(d, StanzaError)
    791 
    792         element = self.stub.output[-1]
    793         self.assertEquals('presence', element.name, "Need to be presence")
    794         self.assertNotIdentical(None, element.x, 'No muc x element')
    795 
    796         # send back user presence, nick changed
    797         xml = u"""
    798             <presence from='%s/%s' type='error'>
    799                 <x xmlns='http://jabber.org/protocol/muc'/>
    800                 <error type='cancel'>
    801                   <conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
    802                 </error>
    803             </presence>
    804         """ % (self.roomJID, newNick)
    805         self.stub.send(parseXml(xml))
     748
     749        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
     750        self.assertNotIdentical(None, form, 'Missing configuration form')
     751        self.assertEquals('submit', form.formType)
     752
     753        response = toResponse(iq, 'result')
     754        self.stub.send(response)
     755        return d
     756
     757
     758    def test_configureCancel(self):
     759        """
     760        Cancelling room configuration should send a cancel form.
     761        """
     762
     763        d = self.protocol.configure(self.roomJID, None)
     764
     765        iq = self.stub.output[-1]
     766
     767        query = "/iq/query[@xmlns='%s']" % (muc.NS_MUC_OWNER)
     768        nodes = xpath.queryForNodes(query, iq)
     769
     770        form = data_form.findForm(nodes[0], muc.NS_MUC_CONFIG)
     771        self.assertNotIdentical(None, form, 'Missing configuration form')
     772        self.assertEquals('cancel', form.formType)
     773
     774        response = toResponse(iq, 'result')
     775        self.stub.send(response)
     776        return d
     777
     778
     779    def test_getMemberList(self):
     780        """
     781        Retrieving the member list returns a list of L{muc.AdminItem}s
     782
     783        The request asks for the affiliation C{'member'}.
     784        """
     785        def cb(items):
     786            self.assertEquals(1, len(items))
     787            item = items[0]
     788            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
     789            self.assertEquals(u'thirdwitch', item.nick)
     790            self.assertEquals(u'member', item.affiliation)
     791
     792        d = self.protocol.getMemberList(self.roomJID)
     793        d.addCallback(cb)
     794
     795        iq = self.stub.output[-1]
     796        self.assertEquals('get', iq.getAttribute('type'))
     797        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
     798                                                              muc.NS_MUC_ADMIN)
     799        items = xpath.queryForNodes(query, iq)
     800        self.assertNotIdentical(None, items)
     801        self.assertEquals(1, len(items))
     802        self.assertEquals('member', items[0].getAttribute('affiliation'))
     803
     804        response = toResponse(iq, 'result')
     805        query = response.addElement((NS_MUC_ADMIN, 'query'))
     806        item = query.addElement('item')
     807        item['affiliation'] ='member'
     808        item['jid'] = 'hag66@shakespeare.lit'
     809        item['nick'] = 'thirdwitch'
     810        item['role'] = 'participant'
     811        self.stub.send(response)
     812
     813        return d
     814
     815
     816    def test_getAdminList(self):
     817        """
     818        Retrieving the admin list returns a list of L{muc.AdminItem}s
     819
     820        The request asks for the affiliation C{'admin'}.
     821        """
     822        d = self.protocol.getAdminList(self.roomJID)
     823
     824        iq = self.stub.output[-1]
     825        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
     826                                                              muc.NS_MUC_ADMIN)
     827        items = xpath.queryForNodes(query, iq)
     828        self.assertEquals('admin', items[0].getAttribute('affiliation'))
     829
     830        response = toResponse(iq, 'result')
     831        query = response.addElement((NS_MUC_ADMIN, 'query'))
     832        self.stub.send(response)
     833
     834        return d
     835
     836
     837    def test_getBanList(self):
     838        """
     839        Retrieving the ban list returns a list of L{muc.AdminItem}s
     840
     841        The request asks for the affiliation C{'outcast'}.
     842        """
     843        def cb(items):
     844            self.assertEquals(1, len(items))
     845            item = items[0]
     846            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
     847            self.assertEquals(u'outcast', item.affiliation)
     848            self.assertEquals(u'Trouble making', item.reason)
     849
     850        d = self.protocol.getBanList(self.roomJID)
     851        d.addCallback(cb)
     852
     853        iq = self.stub.output[-1]
     854        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
     855                                                              muc.NS_MUC_ADMIN)
     856        items = xpath.queryForNodes(query, iq)
     857        self.assertEquals('outcast', items[0].getAttribute('affiliation'))
     858
     859        response = toResponse(iq, 'result')
     860        query = response.addElement((NS_MUC_ADMIN, 'query'))
     861        item = query.addElement('item')
     862        item['affiliation'] ='outcast'
     863        item['jid'] = 'hag66@shakespeare.lit'
     864        item.addElement('reason', content='Trouble making')
     865        self.stub.send(response)
     866
     867        return d
     868
     869
     870    def test_getOwnerList(self):
     871        """
     872        Retrieving the owner list returns a list of L{muc.AdminItem}s
     873
     874        The request asks for the affiliation C{'owner'}.
     875        """
     876        d = self.protocol.getOwnerList(self.roomJID)
     877
     878        iq = self.stub.output[-1]
     879        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
     880                                                              muc.NS_MUC_ADMIN)
     881        items = xpath.queryForNodes(query, iq)
     882        self.assertEquals('owner', items[0].getAttribute('affiliation'))
     883
     884        response = toResponse(iq, 'result')
     885        query = response.addElement((NS_MUC_ADMIN, 'query'))
     886        self.stub.send(response)
     887
     888        return d
     889
     890
     891    def test_getModeratorList(self):
     892        """
     893        Retrieving the moderator returns a list of L{muc.AdminItem}s.
     894
     895        The request asks for the role C{'moderator'}.
     896        """
     897
     898        def cb(items):
     899            self.assertEquals(1, len(items))
     900            item = items[0]
     901            self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
     902            self.assertEquals(u'thirdwitch', item.nick)
     903            self.assertEquals(u'moderator', item.role)
     904
     905        d = self.protocol.getModeratorList(self.roomJID)
     906        d.addCallback(cb)
     907
     908        iq = self.stub.output[-1]
     909        self.assertEquals('get', iq.getAttribute('type'))
     910        query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
     911                                                              muc.NS_MUC_ADMIN)
     912        items = xpath.queryForNodes(query, iq)
     913        self.assertNotIdentical(None, items)
     914        self.assertEquals(1, len(items))
     915        self.assertEquals('moderator', items[0].getAttribute('role'))
     916
     917        response = toResponse(iq, 'result')
     918        query = response.addElement((NS_MUC_ADMIN, 'query'))
     919        item = query.addElement('item')
     920        item['affiliation'] ='member'
     921        item['jid'] = 'hag66@shakespeare.lit'
     922        item['nick'] = 'thirdwitch'
     923        item['role'] = 'moderator'
     924        self.stub.send(response)
     925
    806926        return d
    807927
     
    8941014
    8951015
     1016    def test_ban(self):
     1017        """
     1018        Ban an entity in a room.
     1019        """
     1020        banned = JID('ban@jabber.org/TroubleMaker')
     1021
     1022        def cb(banned):
     1023            self.assertTrue(banned, 'Did not ban user')
     1024
     1025        d = self.protocol.ban(self.roomJID, banned, reason='Spam',
     1026                              sender=self.userJID)
     1027        d.addCallback(cb)
     1028
     1029        iq = self.stub.output[-1]
     1030
     1031        self.assertTrue(xpath.matches(
     1032                u"/iq[@type='set' and @to='%s']/query/item"
     1033                    "[@affiliation='outcast']" % (self.roomJID,),
     1034                iq),
     1035            'Wrong ban stanza')
     1036
     1037        response = toResponse(iq, 'result')
     1038        self.stub.send(response)
     1039
     1040        return d
     1041
     1042
     1043    def test_kick(self):
     1044        """
     1045        Kick an entity from a room.
     1046        """
     1047        nick = 'TroubleMaker'
     1048
     1049        def cb(kicked):
     1050            self.assertTrue(kicked, 'Did not kick user')
     1051
     1052        d = self.protocol.kick(self.roomJID, nick, reason='Spam',
     1053                               sender=self.userJID)
     1054        d.addCallback(cb)
     1055
     1056        iq = self.stub.output[-1]
     1057
     1058        self.assertTrue(xpath.matches(
     1059                u"/iq[@type='set' and @to='%s']/query/item"
     1060                    "[@role='none']" % (self.roomJID,),
     1061                iq),
     1062            'Wrong kick stanza')
     1063
     1064        response = toResponse(iq, 'result')
     1065        self.stub.send(response)
     1066
     1067        return d
     1068
     1069
     1070    def test_destroy(self):
     1071        """
     1072        Destroy a room.
     1073        """
     1074        d = self.protocol.destroy(self.occupantJID, reason='Time to leave',
     1075                                  alternate=JID('other@%s' % self.service),
     1076                                  password='secret')
     1077
     1078        iq = self.stub.output[-1]
     1079
     1080        query = ("/iq/query[@xmlns='%s']/destroy[@xmlns='%s']" %
     1081                 (muc.NS_MUC_OWNER, muc.NS_MUC_OWNER))
     1082
     1083        nodes = xpath.queryForNodes(query, iq)
     1084        self.assertNotIdentical(None, nodes, 'Bad configure request')
     1085        destroy = nodes[0]
     1086        self.assertEquals('Time to leave', unicode(destroy.reason))
     1087
     1088        response = toResponse(iq, 'result')
     1089        self.stub.send(response)
     1090        return d
     1091
     1092
     1093
     1094class MUCClientTest(unittest.TestCase):
     1095    """
     1096    Tests for C{muc.MUCClient}.
     1097    """
     1098
     1099    def setUp(self):
     1100        self.clock = task.Clock()
     1101        self.sessionManager = TestableStreamManager(reactor=self.clock)
     1102        self.stub = self.sessionManager.stub
     1103        self.protocol = muc.MUCClient(reactor=self.clock)
     1104        self.protocol.setHandlerParent(self.sessionManager)
     1105
     1106        self.roomIdentifier = 'test'
     1107        self.service  = 'conference.example.org'
     1108        self.nick = 'Nick'
     1109
     1110        self.occupantJID = JID(tuple=(self.roomIdentifier,
     1111                                      self.service,
     1112                                      self.nick))
     1113        self.roomJID = self.occupantJID.userhostJID()
     1114        self.userJID = JID('test@example.org/Testing')
     1115
     1116
     1117    def _createRoom(self):
     1118        """
     1119        A helper method to create a test room.
     1120        """
     1121        # create a room
     1122        room = muc.Room(self.roomIdentifier,
     1123                        self.service,
     1124                        self.nick)
     1125        self.protocol._addRoom(room)
     1126
     1127
     1128    def test_interface(self):
     1129        """
     1130        Do instances of L{muc.MUCClient} provide L{iwokkel.IMUCClient}?
     1131        """
     1132        verify.verifyObject(iwokkel.IMUCClient, self.protocol)
     1133
     1134
     1135    def test_userJoinedRoom(self):
     1136        """
     1137        Joins by others to a room we're in are passed to userJoinedRoom
     1138        """
     1139        xml = """
     1140            <presence to='%s' from='%s'>
     1141              <x xmlns='http://jabber.org/protocol/muc#user'>
     1142                <item affiliation='member' role='participant'/>
     1143              </x>
     1144            </presence>
     1145        """ % (self.userJID.full(), self.occupantJID.full())
     1146
     1147        # create a room
     1148        self._createRoom()
     1149
     1150        def userJoinedRoom(room, user):
     1151            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
     1152                              'Wrong room name')
     1153            self.assertTrue(room.inRoster(user), 'User not in roster')
     1154
     1155        d, self.protocol.userJoinedRoom = calledAsync(userJoinedRoom)
     1156        self.stub.send(parseXml(xml))
     1157        return d
     1158
     1159
     1160    def test_receivedSubject(self):
     1161        """
     1162        Subject received from a room we're in are passed to receivedSubject.
     1163        """
     1164        xml = u"""
     1165            <message to='%s' from='%s' type='groupchat'>
     1166              <subject>test</subject>
     1167            </message>
     1168        """ % (self.userJID, self.occupantJID)
     1169
     1170        self._createRoom()
     1171
     1172        # add user to room
     1173        user = muc.User(self.nick)
     1174        room = self.protocol._getRoom(self.roomJID)
     1175        room.addUser(user)
     1176
     1177        def receivedSubject(room, user, subject):
     1178            self.assertEquals('test', subject, "Wrong group chat message")
     1179            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
     1180                              'Wrong room name')
     1181            self.assertEquals(self.nick, user.nick)
     1182
     1183        d, self.protocol.receivedSubject = calledAsync(receivedSubject)
     1184        self.stub.send(parseXml(xml))
     1185        return d
     1186
     1187
     1188    def test_receivedGroupChat(self):
     1189        """
     1190        Messages received from a room we're in are passed to receivedGroupChat.
     1191        """
     1192        xml = u"""
     1193            <message to='test@test.com' from='%s' type='groupchat'>
     1194              <body>test</body>
     1195            </message>
     1196        """ % (self.occupantJID)
     1197
     1198        self._createRoom()
     1199
     1200        def receivedGroupChat(room, user, message):
     1201            self.assertEquals('test', message.body, "Wrong group chat message")
     1202            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
     1203                              'Wrong room name')
     1204
     1205        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
     1206        self.stub.send(parseXml(xml))
     1207        return d
     1208
     1209
     1210    def test_receivedGroupChatRoom(self):
     1211        """
     1212        Messages received from the room itself have C{user} set to C{None}.
     1213        """
     1214        xml = u"""
     1215            <message to='test@test.com' from='%s' type='groupchat'>
     1216              <body>test</body>
     1217            </message>
     1218        """ % (self.roomJID)
     1219
     1220        self._createRoom()
     1221
     1222        def receivedGroupChat(room, user, message):
     1223            self.assertIdentical(None, user)
     1224
     1225        d, self.protocol.receivedGroupChat = calledAsync(receivedGroupChat)
     1226        self.stub.send(parseXml(xml))
     1227        return d
     1228
     1229
     1230    def test_join(self):
     1231        """
     1232        Joining a room waits for confirmation, deferred fires room.
     1233        """
     1234
     1235        def cb(room):
     1236            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
     1237
     1238        d = self.protocol.join(self.roomJID, self.nick)
     1239        d.addCallback(cb)
     1240
     1241        # send back user presence, they joined
     1242        xml = """
     1243            <presence from='%s@%s/%s'>
     1244              <x xmlns='http://jabber.org/protocol/muc#user'>
     1245                <item affiliation='member' role='participant'/>
     1246              </x>
     1247            </presence>
     1248        """ % (self.roomIdentifier, self.service, self.nick)
     1249        self.stub.send(parseXml(xml))
     1250        return d
     1251
     1252
     1253    def test_userLeftRoom(self):
     1254        """
     1255        Unavailable presence from a participant removes it from the room.
     1256        """
     1257
     1258        xml = u"""
     1259            <presence to='%s' from='%s' type='unavailable'/>
     1260        """ % (self.userJID, self.occupantJID)
     1261
     1262        # create a room
     1263        self._createRoom()
     1264
     1265        # add user to room
     1266        user = muc.User(self.nick)
     1267        room = self.protocol._getRoom(self.roomJID)
     1268        room.addUser(user)
     1269
     1270        def userLeftRoom(room, user):
     1271            self.assertEquals(self.roomIdentifier, room.roomIdentifier,
     1272                              'Wrong room name')
     1273            self.assertFalse(room.inRoster(user), 'User in roster')
     1274
     1275        d, self.protocol.userLeftRoom = calledAsync(userLeftRoom)
     1276        self.stub.send(parseXml(xml))
     1277        return d
     1278
     1279
     1280    def test_receivedHistory(self):
     1281        """
     1282        Receiving history on room join.
     1283        """
     1284        xml = u"""
     1285            <message to='test@test.com' from='%s' type='groupchat'>
     1286              <body>test</body>
     1287              <delay xmlns='urn:xmpp:delay' stamp="2002-10-13T23:58:37Z"
     1288                                            from="%s"/>
     1289            </message>
     1290        """ % (self.occupantJID, self.userJID)
     1291
     1292        self._createRoom()
     1293
     1294
     1295        def receivedHistory(room, user, message):
     1296            self.assertEquals('test', message.body, "wrong message body")
     1297            stamp = datetime(2002, 10, 13, 23, 58, 37, tzinfo=tzutc())
     1298            self.assertEquals(stamp, message.delay.stamp,
     1299                             'Does not have a history stamp')
     1300
     1301        d, self.protocol.receivedHistory = calledAsync(receivedHistory)
     1302        self.stub.send(parseXml(xml))
     1303        return d
     1304
     1305
     1306    def test_nick(self):
     1307        """
     1308        Send a nick change to the server.
     1309        """
     1310        newNick = 'newNick'
     1311
     1312        self._createRoom()
     1313
     1314        def cb(room):
     1315            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
     1316            self.assertEquals(newNick, room.nick)
     1317
     1318        d = self.protocol.nick(self.roomJID, newNick)
     1319        d.addCallback(cb)
     1320
     1321        # send back user presence, nick changed
     1322        xml = u"""
     1323            <presence from='%s/%s'>
     1324              <x xmlns='http://jabber.org/protocol/muc#user'>
     1325                <item affiliation='member' role='participant'/>
     1326              </x>
     1327            </presence>
     1328        """ % (self.roomJID, newNick)
     1329        self.stub.send(parseXml(xml))
     1330        return d
     1331
     1332
    8961333    def test_status(self):
    8971334        """
    8981335        Change status
    8991336        """
    900         self._createRoom()
    901         room = self.protocol._getRoom(self.roomJID)
    902         user = muc.User(self.nick)
    903         room.addUser(user)
    904 
    905         def cb(room):
     1337        def joined(_):
     1338            d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
     1339            d.addCallback(statusChanged)
     1340            return d
     1341
     1342        def statusChanged(room):
    9061343            self.assertEquals(self.roomIdentifier, room.roomIdentifier)
    9071344            user = room.getUser(self.nick)
     
    9101347            self.assertEquals('xa', user.show, 'Wrong show')
    9111348
    912         d = self.protocol.status(self.roomJID, 'xa', 'testing MUC')
    913         d.addCallback(cb)
    914 
    915         element = self.stub.output[-1]
    916 
    917         self.assertEquals('presence', element.name, "Need to be presence")
    918         self.assertTrue(getattr(element, 'x', None), 'No muc x element')
     1349        # Join the room
     1350        d = self.protocol.join(self.roomJID, self.nick)
     1351        d.addCallback(joined)
     1352
     1353        # Receive presence back from the room: joined.
     1354        xml = u"""
     1355            <presence to='%s' from='%s'/>
     1356        """ % (self.userJID, self.occupantJID)
     1357        self.stub.send(parseXml(xml))
    9191358
    9201359        # send back user presence, status changed
     
    9291368        """ % self.occupantJID
    9301369        self.stub.send(parseXml(xml))
    931         return d
    932 
    933 
    934     def test_getMemberList(self):
    935         """
    936         Retrieving the member list returns a list of L{muc.AdminItem}s
    937 
    938         The request asks for the affiliation C{'member'}.
    939         """
    940         def cb(items):
    941             self.assertEquals(1, len(items))
    942             item = items[0]
    943             self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
    944             self.assertEquals(u'thirdwitch', item.nick)
    945             self.assertEquals(u'member', item.affiliation)
    946 
    947         d = self.protocol.getMemberList(self.roomJID)
    948         d.addCallback(cb)
    949 
    950         iq = self.stub.output[-1]
    951         self.assertEquals('get', iq.getAttribute('type'))
    952         query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
    953                                                               muc.NS_MUC_ADMIN)
    954         items = xpath.queryForNodes(query, iq)
    955         self.assertNotIdentical(None, items)
    956         self.assertEquals(1, len(items))
    957         self.assertEquals('member', items[0].getAttribute('affiliation'))
    958 
    959         response = toResponse(iq, 'result')
    960         query = response.addElement((NS_MUC_ADMIN, 'query'))
    961         item = query.addElement('item')
    962         item['affiliation'] ='member'
    963         item['jid'] = 'hag66@shakespeare.lit'
    964         item['nick'] = 'thirdwitch'
    965         item['role'] = 'participant'
    966         self.stub.send(response)
    967 
    968         return d
    969 
    970 
    971     def test_getAdminList(self):
    972         """
    973         Retrieving the admin list returns a list of L{muc.AdminItem}s
    974 
    975         The request asks for the affiliation C{'admin'}.
    976         """
    977         d = self.protocol.getAdminList(self.roomJID)
    978 
    979         iq = self.stub.output[-1]
    980         query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
    981                                                               muc.NS_MUC_ADMIN)
    982         items = xpath.queryForNodes(query, iq)
    983         self.assertEquals('admin', items[0].getAttribute('affiliation'))
    984 
    985         response = toResponse(iq, 'result')
    986         query = response.addElement((NS_MUC_ADMIN, 'query'))
    987         self.stub.send(response)
    988 
    989         return d
    990 
    991 
    992     def test_getBanList(self):
    993         """
    994         Retrieving the ban list returns a list of L{muc.AdminItem}s
    995 
    996         The request asks for the affiliation C{'outcast'}.
    997         """
    998         def cb(items):
    999             self.assertEquals(1, len(items))
    1000             item = items[0]
    1001             self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
    1002             self.assertEquals(u'outcast', item.affiliation)
    1003             self.assertEquals(u'Trouble making', item.reason)
    1004 
    1005         d = self.protocol.getBanList(self.roomJID)
    1006         d.addCallback(cb)
    1007 
    1008         iq = self.stub.output[-1]
    1009         query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
    1010                                                               muc.NS_MUC_ADMIN)
    1011         items = xpath.queryForNodes(query, iq)
    1012         self.assertEquals('outcast', items[0].getAttribute('affiliation'))
    1013 
    1014         response = toResponse(iq, 'result')
    1015         query = response.addElement((NS_MUC_ADMIN, 'query'))
    1016         item = query.addElement('item')
    1017         item['affiliation'] ='outcast'
    1018         item['jid'] = 'hag66@shakespeare.lit'
    1019         item.addElement('reason', content='Trouble making')
    1020         self.stub.send(response)
    1021 
    1022         return d
    1023 
    1024 
    1025     def test_getOwnerList(self):
    1026         """
    1027         Retrieving the owner list returns a list of L{muc.AdminItem}s
    1028 
    1029         The request asks for the affiliation C{'owner'}.
    1030         """
    1031         d = self.protocol.getOwnerList(self.roomJID)
    1032 
    1033         iq = self.stub.output[-1]
    1034         query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
    1035                                                               muc.NS_MUC_ADMIN)
    1036         items = xpath.queryForNodes(query, iq)
    1037         self.assertEquals('owner', items[0].getAttribute('affiliation'))
    1038 
    1039         response = toResponse(iq, 'result')
    1040         query = response.addElement((NS_MUC_ADMIN, 'query'))
    1041         self.stub.send(response)
    1042 
    1043         return d
    1044 
    1045 
    1046     def test_getModeratorList(self):
    1047         """
    1048         Retrieving the moderator returns a list of L{muc.AdminItem}s.
    1049 
    1050         The request asks for the role C{'moderator'}.
    1051         """
    1052 
    1053         def cb(items):
    1054             self.assertEquals(1, len(items))
    1055             item = items[0]
    1056             self.assertEquals(JID(u'hag66@shakespeare.lit'), item.entity)
    1057             self.assertEquals(u'thirdwitch', item.nick)
    1058             self.assertEquals(u'moderator', item.role)
    1059 
    1060         d = self.protocol.getModeratorList(self.roomJID)
    1061         d.addCallback(cb)
    1062 
    1063         iq = self.stub.output[-1]
    1064         self.assertEquals('get', iq.getAttribute('type'))
    1065         query = "/iq/query[@xmlns='%s']/item[@xmlns='%s']" % (muc.NS_MUC_ADMIN,
    1066                                                               muc.NS_MUC_ADMIN)
    1067         items = xpath.queryForNodes(query, iq)
    1068         self.assertNotIdentical(None, items)
    1069         self.assertEquals(1, len(items))
    1070         self.assertEquals('moderator', items[0].getAttribute('role'))
    1071 
    1072         response = toResponse(iq, 'result')
    1073         query = response.addElement((NS_MUC_ADMIN, 'query'))
    1074         item = query.addElement('item')
    1075         item['affiliation'] ='member'
    1076         item['jid'] = 'hag66@shakespeare.lit'
    1077         item['nick'] = 'thirdwitch'
    1078         item['role'] = 'moderator'
    1079         self.stub.send(response)
    1080 
    1081         return d
     1370
     1371        return d
Note: See TracChangeset for help on using the changeset viewer.