Changeset 49:50a84c44cbf1


Ignore:
Timestamp:
Jan 12, 2009, 9:00:30 PM (12 years ago)
Author:
Ralph Meijer <ralphm@…>
Branch:
default
Convert:
svn:b33ecbfc-034c-dc11-8662-000475d9059e/trunk@157
Message:

Add Service Discovery client protocol and overhaul data classes.

Author: ralphm.
Reviewer: tofu.
Fixes #28.

Location:
wokkel
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • wokkel/disco.py

    r46 r49  
    11# -*- test-case-name: wokkel.test.test_disco -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) 2003-2009 Ralph Meijer
    44# See LICENSE for details.
    55
     
    1212
    1313from twisted.internet import defer
    14 from twisted.words.protocols.jabber import error, jid
     14from twisted.words.protocols.jabber import error, jid, xmlstream
    1515from twisted.words.xish import domish
    1616
     17from wokkel import data_form
    1718from wokkel.iwokkel import IDisco
    1819from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
     
    2627DISCO_ITEMS = IQ_GET + '/query[@xmlns="' + NS_DISCO_ITEMS + '"]'
    2728
    28 class DiscoFeature(domish.Element):
    29     """
    30     Element representing an XMPP service discovery feature.
    31     """
    32 
    33     def __init__(self, feature):
    34         domish.Element.__init__(self, (NS_DISCO_INFO, 'feature'),
    35                                 attribs={'var': feature})
    36 
    37 
    38 class DiscoIdentity(domish.Element):
    39     """
    40     Element representing an XMPP service discovery identity.
    41     """
    42 
    43     def __init__(self, category, type, name = None):
    44         domish.Element.__init__(self, (NS_DISCO_INFO, 'identity'),
    45                                 attribs={'category': category,
    46                                          'type': type})
    47         if name:
    48             self['name'] = name
    49 
    50 
    51 class DiscoItem(domish.Element):
    52     """
    53     Element representing an XMPP service discovery item.
    54     """
    55 
    56     def __init__(self, jid, node='', name=None):
    57         domish.Element.__init__(self, (NS_DISCO_ITEMS, 'item'),
    58                                 attribs={'jid': jid.full()})
    59         if node:
    60             self['node'] = node
    61 
    62         if name:
    63             self['name'] = name
     29class DiscoFeature(unicode):
     30    """
     31    XMPP service discovery feature.
     32
     33    This extends C{unicode} to convert to and from L{domish.Element}, but
     34    further behaves identically.
     35    """
     36
     37    def toElement(self):
     38        """
     39        Render to a DOM representation.
     40
     41        @rtype: L{domish.Element}.
     42        """
     43        element = domish.Element((NS_DISCO_INFO, 'feature'))
     44        element['var'] = unicode(self)
     45        return element
     46
     47
     48    @staticmethod
     49    def fromElement(element):
     50        """
     51        Parse a DOM representation into a L{DiscoFeature} instance.
     52
     53        @param element: Element that represents the disco feature.
     54        @type element: L{domish.Element}.
     55        @rtype L{DiscoFeature}.
     56        """
     57        featureURI = element.getAttribute('var', u'')
     58        feature = DiscoFeature(featureURI)
     59        return feature
     60
     61
     62
     63class DiscoIdentity(object):
     64    """
     65    XMPP service discovery identity.
     66
     67    @ivar category: The identity category.
     68    @type category: C{unicode}
     69    @ivar type: The identity type.
     70    @type type: C{unicode}
     71    @ivar name: The optional natural language name for this entity.
     72    @type name: C{unicode}
     73    """
     74
     75    def __init__(self, category, idType, name=None):
     76        self.category = category
     77        self.type = idType
     78        self.name = name
     79
     80
     81    def toElement(self):
     82        """
     83        Generate a DOM representation.
     84
     85        @rtype: L{domish.Element}.
     86        """
     87        element = domish.Element((NS_DISCO_INFO, 'identity'))
     88        if self.category:
     89            element['category'] = self.category
     90        if self.type:
     91            element['type'] = self.type
     92        if self.name:
     93            element['name'] = self.name
     94        return element
     95
     96
     97    @staticmethod
     98    def fromElement(element):
     99        """
     100        Parse a DOM representation into a L{DiscoIdentity} instance.
     101
     102        @param element: Element that represents the disco identity.
     103        @type element: L{domish.Element}.
     104        @rtype L{DiscoIdentity}.
     105        """
     106        category = element.getAttribute('category')
     107        idType = element.getAttribute('type')
     108        name = element.getAttribute('name')
     109        feature = DiscoIdentity(category, idType, name)
     110        return feature
     111
     112
     113
     114class DiscoInfo(object):
     115    """
     116    XMPP service discovery info.
     117
     118    @ivar nodeIdentifier: The optional node this info applies to.
     119    @type nodeIdentifier: C{unicode}
     120    @ivar features: Features as L{DiscoFeature}.
     121    @type features: C{set)
     122    @ivar identities: Identities as a mapping from (category, type) to name,
     123                      all C{unicode}.
     124    @type identities: C{dict}
     125    @ivar extensions: Service discovery extensions as a mapping from the
     126                      extension form's C{FORM_TYPE} (C{unicode}) to
     127                      L{data_form.Form}. Forms with no C{FORM_TYPE} field
     128                      are mapped as C{None}. Note that multiple forms
     129                      with the same C{FORM_TYPE} have the last in sequence
     130                      prevail.
     131    @type extensions: C{dict}
     132    @ivar _items: Sequence of added items.
     133    @type _items: C{list}
     134    """
     135
     136    def __init__(self):
     137        self.nodeIdentifier = ''
     138        self.features = set()
     139        self.identities = {}
     140        self.extensions = {}
     141        self._items = []
     142
     143
     144    def __iter__(self):
     145        """
     146        Iterator over sequence of items in the order added.
     147        """
     148        return iter(self._items)
     149
     150
     151    def append(self, item):
     152        """
     153        Add a piece of service discovery info.
     154
     155        @param item: A feature, identity or extension form.
     156        @type item: L{DiscoFeature}, L{DiscoIdentity} or L{data_form.Form}
     157        """
     158        self._items.append(item)
     159
     160        if isinstance(item, DiscoFeature):
     161            self.features.add(item)
     162        elif isinstance(item, DiscoIdentity):
     163            self.identities[(item.category, item.type)] = item.name
     164        elif isinstance(item, data_form.Form):
     165            self.extensions[item.formNamespace] = item
     166
     167
     168    def toElement(self):
     169        """
     170        Generate a DOM representation.
     171
     172        This takes the items added with C{append} to create a DOM
     173        representation of service discovery information.
     174
     175        @rtype: L{domish.Element}.
     176        """
     177        element = domish.Element((NS_DISCO_INFO, 'query'))
     178
     179        if self.nodeIdentifier:
     180            element['node'] = self.nodeIdentifier
     181
     182        for item in self:
     183            element.addChild(item.toElement())
     184
     185        return element
     186
     187
     188    @staticmethod
     189    def fromElement(element):
     190        """
     191        Parse a DOM representation into a L{DiscoInfo} instance.
     192
     193        @param element: Element that represents the disco info.
     194        @type element: L{domish.Element}.
     195        @rtype L{DiscoInfo}.
     196        """
     197
     198        info = DiscoInfo()
     199
     200        info.nodeIdentifier = element.getAttribute('node', '')
     201
     202        for child in element.elements():
     203            item = None
     204
     205            if (child.uri, child.name) == (NS_DISCO_INFO, 'feature'):
     206                item = DiscoFeature.fromElement(child)
     207            elif (child.uri, child.name) == (NS_DISCO_INFO, 'identity'):
     208                item = DiscoIdentity.fromElement(child)
     209            elif (child.uri, child.name) == (data_form.NS_X_DATA, 'x'):
     210                item = data_form.Form.fromElement(child)
     211
     212            if item:
     213                info.append(item)
     214
     215        return info
     216
     217
     218
     219class DiscoItem(object):
     220    """
     221    XMPP service discovery item.
     222
     223    @ivar entity: The entity holding the item.
     224    @type entity: L{jid.JID}
     225    @ivar nodeIdentifier: The optional node identifier for the item.
     226    @type nodeIdentifier: C{unicode}
     227    @ivar name: The optional natural language name for this entity.
     228    @type name: C{unicode}
     229    """
     230
     231    def __init__(self, entity, nodeIdentifier='', name=None):
     232        self.entity = entity
     233        self.nodeIdentifier = nodeIdentifier
     234        self.name = name
     235
     236
     237    def toElement(self):
     238        """
     239        Generate a DOM representation.
     240
     241        @rtype: L{domish.Element}.
     242        """
     243        element = domish.Element((NS_DISCO_ITEMS, 'item'))
     244        if self.entity:
     245            element['jid'] = self.entity.full()
     246        if self.nodeIdentifier:
     247            element['node'] = self.nodeIdentifier
     248        if self.name:
     249            element['name'] = self.name
     250        return element
     251
     252
     253    @staticmethod
     254    def fromElement(element):
     255        """
     256        Parse a DOM representation into a L{DiscoItem} instance.
     257
     258        @param element: Element that represents the disco iitem.
     259        @type element: L{domish.Element}.
     260        @rtype L{DiscoItem}.
     261        """
     262        try:
     263            entity = jid.JID(element.getAttribute('jid', ' '))
     264        except jid.InvalidFormat:
     265            entity = None
     266        nodeIdentifier = element.getAttribute('node', '')
     267        name = element.getAttribute('name')
     268        feature = DiscoItem(entity, nodeIdentifier, name)
     269        return feature
     270
     271
     272
     273class DiscoItems(object):
     274    """
     275    XMPP service discovery items.
     276
     277    @ivar nodeIdentifier: The optional node this info applies to.
     278    @type nodeIdentifier: C{unicode}
     279    @ivar _items: Sequence of added items.
     280    @type _items: C{list}
     281    """
     282
     283    def __init__(self):
     284        self.nodeIdentifier = ''
     285        self._items = []
     286
     287
     288    def __iter__(self):
     289        """
     290        Iterator over sequence of items in the order added.
     291        """
     292        return iter(self._items)
     293
     294
     295    def append(self, item):
     296        """
     297        Append item to the sequence of items.
     298
     299        @param item: Item to be added.
     300        @type item: L{DiscoItem}
     301        """
     302        self._items.append(item)
     303
     304
     305    def toElement(self):
     306        """
     307        Generate a DOM representation.
     308
     309        This takes the items added with C{append} to create a DOM
     310        representation of service discovery items.
     311
     312        @rtype: L{domish.Element}.
     313        """
     314        element = domish.Element((NS_DISCO_ITEMS, 'query'))
     315
     316        if self.nodeIdentifier:
     317            element['node'] = self.nodeIdentifier
     318
     319        for item in self:
     320            element.addChild(item.toElement())
     321
     322        return element
     323
     324
     325    @staticmethod
     326    def fromElement(element):
     327        """
     328        Parse a DOM representation into a L{DiscoItems} instance.
     329
     330        @param element: Element that represents the disco items.
     331        @type element: L{domish.Element}.
     332        @rtype L{DiscoItems}.
     333        """
     334
     335        info = DiscoItems()
     336
     337        info.nodeIdentifier = element.getAttribute('node', '')
     338
     339        for child in element.elements():
     340            if (child.uri, child.name) == (NS_DISCO_ITEMS, 'item'):
     341                item = DiscoItem.fromElement(child)
     342                info.append(item)
     343
     344        return info
     345
     346
     347
     348class _DiscoRequest(xmlstream.IQ):
     349    """
     350    Element representing an XMPP service discovery request.
     351    """
     352
     353    def __init__(self, xs, namespace, nodeIdentifier=''):
     354        """
     355        Initialize the request.
     356
     357        @param xs: XML Stream the request should go out on.
     358        @type xs: L{xmlstream.XmlStream}
     359        @param namespace: Request namespace.
     360        @type namespace: C{str}
     361        @param nodeIdentifier: Node to request info from.
     362        @type nodeIdentifier: C{unicode}
     363        """
     364        xmlstream.IQ.__init__(self, xs, "get")
     365        query = self.addElement((namespace, 'query'))
     366        if nodeIdentifier:
     367            query['node'] = nodeIdentifier
     368
     369
     370
     371class DiscoClientProtocol(XMPPHandler):
     372    """
     373    XMPP Service Discovery client protocol.
     374    """
     375
     376    def requestInfo(self, entity, nodeIdentifier=''):
     377        """
     378        Request information discovery from a node.
     379
     380        @param entity: Entity to send the request to.
     381        @type entity: L{jid.JID}
     382        @param nodeIdentifier: Optional node to request info from.
     383        @type nodeIdentifier: C{unicode}
     384        """
     385
     386        request = _DiscoRequest(self.xmlstream, NS_DISCO_INFO, nodeIdentifier)
     387
     388        d = request.send(entity.full())
     389        d.addCallback(lambda iq: DiscoInfo.fromElement(iq.query))
     390        return d
     391
     392
     393    def requestItems(self, entity, nodeIdentifier=''):
     394        """
     395        Request items discovery from a node.
     396
     397        @param entity: Entity to send the request to.
     398        @type entity: L{jid.JID}
     399        @param nodeIdentifier: Optional node to request info from.
     400        @type nodeIdentifier: C{unicode}
     401        """
     402
     403        request = _DiscoRequest(self.xmlstream, NS_DISCO_ITEMS, nodeIdentifier)
     404
     405        d = request.send(entity.full())
     406        d.addCallback(lambda iq: DiscoItems.fromElement(iq.query))
     407        return d
    64408
    65409
     
    97441                raise error.StanzaError('item-not-found')
    98442            else:
    99                 response = domish.Element((NS_DISCO_INFO, 'query'))
    100                 if nodeIdentifier:
    101                     response['node'] = nodeIdentifier
     443                response = DiscoInfo()
     444                response.nodeIdentifier = nodeIdentifier
    102445
    103446                for item in info:
    104                     response.addChild(item)
    105 
    106             return response
     447                    response.append(item)
     448
     449            return response.toElement()
    107450
    108451        d = self.info(requestor, target, nodeIdentifier)
     
    123466
    124467        def toResponse(items):
    125             response = domish.Element((NS_DISCO_ITEMS, 'query'))
    126             if nodeIdentifier:
    127                 response['node'] = nodeIdentifier
     468            response = DiscoItems()
     469            response.nodeIdentifier = nodeIdentifier
    128470
    129471            for item in items:
    130                 response.addChild(item)
    131 
    132             return response
     472                response.append(item)
     473
     474            return response.toElement()
    133475
    134476        d = self.items(requestor, target, nodeIdentifier)
  • wokkel/pubsub.py

    r47 r49  
    541541
    542542        if not nodeIdentifier:
    543             info.append(disco.DiscoIdentity(**self.discoIdentity))
     543            category, idType, name = self.discoIdentity
     544            info.append(disco.DiscoIdentity(category, idType, name))
    544545
    545546            info.append(disco.DiscoFeature(disco.NS_DISCO_ITEMS))
  • wokkel/test/test_disco.py

    r46 r49  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) 2003-2009 Ralph Meijer
    22# See LICENSE for details.
    33
     
    66"""
    77
     8from zope.interface import implements
     9
    810from twisted.internet import defer
    911from twisted.trial import unittest
    1012from twisted.words.protocols.jabber.jid import JID
    11 from zope.interface import implements
    12 
    13 from wokkel import disco
     13from twisted.words.xish import domish
     14
     15from wokkel import data_form, disco
     16from wokkel.generic import parseXml
    1417from wokkel.subprotocols import XMPPHandler
    15 from wokkel.test.helpers import TestableRequestHandlerMixin
    16 
     18from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub
     19
     20try:
     21    from twisted.words.protocols.jabber.xmlstream import toResponse
     22except ImportError:
     23    from wokkel.compat import toResponse
    1724
    1825NS_DISCO_INFO = 'http://jabber.org/protocol/disco#info'
    1926NS_DISCO_ITEMS = 'http://jabber.org/protocol/disco#items'
     27
     28class DiscoFeatureTest(unittest.TestCase):
     29    """
     30    Tests for L{disco.DiscoFeature}.
     31    """
     32
     33    def test_init(self):
     34        """
     35        Test initialization with a with feature namespace URI.
     36        """
     37        feature = disco.DiscoFeature(u'testns')
     38        self.assertEqual(u'testns', feature)
     39
     40
     41    def test_toElement(self):
     42        """
     43        Test proper rendering to a DOM representation.
     44
     45        The returned element should be properly named and have a C{var}
     46        attribute that holds the feature namespace URI.
     47        """
     48        feature = disco.DiscoFeature(u'testns')
     49        element = feature.toElement()
     50        self.assertEqual(NS_DISCO_INFO, element.uri)
     51        self.assertEqual(u'feature', element.name)
     52        self.assertTrue(element.hasAttribute(u'var'))
     53        self.assertEqual(u'testns', element[u'var'])
     54
     55
     56    def test_fromElement(self):
     57        """
     58        Test creating L{disco.DiscoFeature} from L{domish.Element}.
     59        """
     60        element = domish.Element((NS_DISCO_INFO, u'feature'))
     61        element['var'] = u'testns'
     62        feature = disco.DiscoFeature.fromElement(element)
     63        self.assertEqual(u'testns', feature)
     64
     65
     66
     67class DiscoIdentityTest(unittest.TestCase):
     68    """
     69    Tests for L{disco.DiscoIdentity}.
     70    """
     71
     72    def test_init(self):
     73        """
     74        Test initialization with a category, type and name.
     75        """
     76        identity = disco.DiscoIdentity(u'conference', u'text', u'The chatroom')
     77        self.assertEqual(u'conference', identity.category)
     78        self.assertEqual(u'text', identity.type)
     79        self.assertEqual(u'The chatroom', identity.name)
     80
     81
     82    def test_toElement(self):
     83        """
     84        Test proper rendering to a DOM representation.
     85
     86        The returned element should be properly named and have C{conference},
     87        C{type}, and C{name} attributes.
     88        """
     89        identity = disco.DiscoIdentity(u'conference', u'text', u'The chatroom')
     90        element = identity.toElement()
     91        self.assertEqual(NS_DISCO_INFO, element.uri)
     92        self.assertEqual(u'identity', element.name)
     93        self.assertEqual(u'conference', element.getAttribute(u'category'))
     94        self.assertEqual(u'text', element.getAttribute(u'type'))
     95        self.assertEqual(u'The chatroom', element.getAttribute(u'name'))
     96
     97
     98    def test_toElementWithoutName(self):
     99        """
     100        Test proper rendering to a DOM representation without a name.
     101
     102        The returned element should be properly named and have C{conference},
     103        C{type} attributes, no C{name} attribute.
     104        """
     105        identity = disco.DiscoIdentity(u'conference', u'text')
     106        element = identity.toElement()
     107        self.assertEqual(NS_DISCO_INFO, element.uri)
     108        self.assertEqual(u'identity', element.name)
     109        self.assertEqual(u'conference', element.getAttribute(u'category'))
     110        self.assertEqual(u'text', element.getAttribute(u'type'))
     111        self.assertFalse(element.hasAttribute(u'name'))
     112
     113
     114    def test_fromElement(self):
     115        """
     116        Test creating L{disco.DiscoIdentity} from L{domish.Element}.
     117        """
     118        element = domish.Element((NS_DISCO_INFO, u'identity'))
     119        element['category'] = u'conference'
     120        element['type'] = u'text'
     121        element['name'] = u'The chatroom'
     122        identity = disco.DiscoIdentity.fromElement(element)
     123        self.assertEqual(u'conference', identity.category)
     124        self.assertEqual(u'text', identity.type)
     125        self.assertEqual(u'The chatroom', identity.name)
     126
     127
     128    def test_fromElementWithoutName(self):
     129        """
     130        Test creating L{disco.DiscoIdentity} from L{domish.Element}, no name.
     131        """
     132        element = domish.Element((NS_DISCO_INFO, u'identity'))
     133        element['category'] = u'conference'
     134        element['type'] = u'text'
     135        identity = disco.DiscoIdentity.fromElement(element)
     136        self.assertEqual(u'conference', identity.category)
     137        self.assertEqual(u'text', identity.type)
     138        self.assertEqual(None, identity.name)
     139
     140
     141
     142class DiscoInfoTest(unittest.TestCase):
     143    """
     144    Tests for L{disco.DiscoInfo}.
     145    """
     146
     147    def test_toElement(self):
     148        """
     149        Test C{toElement} creates a correctly namespaced element, no node.
     150        """
     151        info = disco.DiscoInfo()
     152        element = info.toElement()
     153
     154        self.assertEqual(NS_DISCO_INFO, element.uri)
     155        self.assertEqual(u'query', element.name)
     156        self.assertFalse(element.hasAttribute(u'node'))
     157
     158
     159    def test_toElementNode(self):
     160        """
     161        Test C{toElement} with a node.
     162        """
     163        info = disco.DiscoInfo()
     164        info.nodeIdentifier = u'test'
     165        element = info.toElement()
     166
     167        self.assertEqual(u'test', element.getAttribute(u'node'))
     168
     169
     170    def test_toElementChildren(self):
     171        """
     172        Test C{toElement} creates a DOM with proper childs.
     173        """
     174        info = disco.DiscoInfo()
     175        info.append(disco.DiscoFeature(u'jabber:iq:register'))
     176        info.append(disco.DiscoIdentity(u'conference', u'text'))
     177        info.append(data_form.Form(u'result'))
     178        element = info.toElement()
     179
     180        featureElements = domish.generateElementsQNamed(element.children,
     181                                                        u'feature',
     182                                                        NS_DISCO_INFO)
     183        self.assertEqual(1, len(list(featureElements)))
     184
     185        identityElements = domish.generateElementsQNamed(element.children,
     186                                                         u'identity',
     187                                                         NS_DISCO_INFO)
     188        self.assertEqual(1, len(list(identityElements)))
     189
     190        extensionElements = domish.generateElementsQNamed(element.children,
     191                                                         u'x',
     192                                                         data_form.NS_X_DATA)
     193        self.assertEqual(1, len(list(extensionElements)))
     194
     195
     196    def test_fromElement(self):
     197        """
     198        Test properties when creating L{disco.DiscoInfo} from L{domish.Element}.
     199        """
     200        xml = """<query xmlns='http://jabber.org/protocol/disco#info'>
     201                   <identity category='conference'
     202                             type='text'
     203                             name='A Dark Cave'/>
     204                   <feature var='http://jabber.org/protocol/muc'/>
     205                   <feature var='jabber:iq:register'/>
     206                   <x xmlns='jabber:x:data' type='result'>
     207                     <field var='FORM_TYPE' type='hidden'>
     208                       <value>http://jabber.org/protocol/muc#roominfo</value>
     209                     </field>
     210                   </x>
     211                 </query>"""
     212
     213        element = parseXml(xml)
     214        info = disco.DiscoInfo.fromElement(element)
     215
     216        self.assertIn(u'http://jabber.org/protocol/muc', info.features)
     217        self.assertIn(u'jabber:iq:register', info.features)
     218
     219        self.assertIn((u'conference', u'text'), info.identities)
     220        self.assertEqual(u'A Dark Cave',
     221                          info.identities[(u'conference', u'text')])
     222
     223        self.assertIn(u'http://jabber.org/protocol/muc#roominfo',
     224                      info.extensions)
     225
     226
     227    def test_fromElementItems(self):
     228        """
     229        Test items when creating L{disco.DiscoInfo} from L{domish.Element}.
     230        """
     231        xml = """<query xmlns='http://jabber.org/protocol/disco#info'>
     232                   <identity category='conference'
     233                             type='text'
     234                             name='A Dark Cave'/>
     235                   <feature var='http://jabber.org/protocol/muc'/>
     236                   <feature var='jabber:iq:register'/>
     237                   <x xmlns='jabber:x:data' type='result'>
     238                     <field var='FORM_TYPE' type='hidden'>
     239                       <value>http://jabber.org/protocol/muc#roominfo</value>
     240                     </field>
     241                   </x>
     242                 </query>"""
     243
     244        element = parseXml(xml)
     245        info = disco.DiscoInfo.fromElement(element)
     246
     247        info = list(info)
     248        self.assertEqual(4, len(info))
     249
     250        identity = info[0]
     251        self.assertEqual(u'conference', identity.category)
     252
     253        self.assertEqual(u'http://jabber.org/protocol/muc', info[1])
     254        self.assertEqual(u'jabber:iq:register', info[2])
     255
     256        extension = info[3]
     257        self.assertEqual(u'http://jabber.org/protocol/muc#roominfo',
     258                         extension.formNamespace)
     259
     260
     261    def test_fromElementNoNode(self):
     262        """
     263        Test creating L{disco.DiscoInfo} from L{domish.Element}, no node.
     264        """
     265        xml = """<query xmlns='http://jabber.org/protocol/disco#info'/>"""
     266
     267        element = parseXml(xml)
     268        info = disco.DiscoInfo.fromElement(element)
     269
     270        self.assertEqual(u'', info.nodeIdentifier)
     271
     272
     273    def test_fromElementNode(self):
     274        """
     275        Test creating L{disco.DiscoInfo} from L{domish.Element}, with node.
     276        """
     277        xml = """<query xmlns='http://jabber.org/protocol/disco#info'
     278                        node='test'>
     279                 </query>"""
     280
     281        element = parseXml(xml)
     282        info = disco.DiscoInfo.fromElement(element)
     283
     284        self.assertEqual(u'test', info.nodeIdentifier)
     285
     286
     287
     288class DiscoItemTest(unittest.TestCase):
     289    """
     290    Tests for L{disco.DiscoItem}.
     291    """
     292
     293    def test_init(self):
     294        """
     295        Test initialization with a category, type and name.
     296        """
     297        item = disco.DiscoItem(JID(u'example.org'), u'test', u'The node')
     298        self.assertEqual(JID(u'example.org'), item.entity)
     299        self.assertEqual(u'test', item.nodeIdentifier)
     300        self.assertEqual(u'The node', item.name)
     301
     302
     303    def test_toElement(self):
     304        """
     305        Test proper rendering to a DOM representation.
     306
     307        The returned element should be properly named and have C{jid}, C{node},
     308        and C{name} attributes.
     309        """
     310        item = disco.DiscoItem(JID(u'example.org'), u'test', u'The node')
     311        element = item.toElement()
     312        self.assertEqual(NS_DISCO_ITEMS, element.uri)
     313        self.assertEqual(u'item', element.name)
     314        self.assertEqual(u'example.org', element.getAttribute(u'jid'))
     315        self.assertEqual(u'test', element.getAttribute(u'node'))
     316        self.assertEqual(u'The node', element.getAttribute(u'name'))
     317
     318
     319    def test_toElementWithoutName(self):
     320        """
     321        Test proper rendering to a DOM representation without a name.
     322
     323        The returned element should be properly named and have C{jid}, C{node}
     324        attributes, no C{name} attribute.
     325        """
     326        item = disco.DiscoItem(JID(u'example.org'), u'test')
     327        element = item.toElement()
     328        self.assertEqual(NS_DISCO_ITEMS, element.uri)
     329        self.assertEqual(u'item', element.name)
     330        self.assertEqual(u'example.org', element.getAttribute(u'jid'))
     331        self.assertEqual(u'test', element.getAttribute(u'node'))
     332        self.assertFalse(element.hasAttribute(u'name'))
     333
     334
     335    def test_fromElement(self):
     336        """
     337        Test creating L{disco.DiscoItem} from L{domish.Element}.
     338        """
     339        element = domish.Element((NS_DISCO_ITEMS, u'item'))
     340        element[u'jid'] = u'example.org'
     341        element[u'node'] = u'test'
     342        element[u'name'] = u'The node'
     343        item = disco.DiscoItem.fromElement(element)
     344        self.assertEqual(JID(u'example.org'), item.entity)
     345        self.assertEqual(u'test', item.nodeIdentifier)
     346        self.assertEqual(u'The node', item.name)
     347
     348    def test_fromElementNoNode(self):
     349        """
     350        Test creating L{disco.DiscoItem} from L{domish.Element}, no node.
     351        """
     352        element = domish.Element((NS_DISCO_ITEMS, u'item'))
     353        element[u'jid'] = u'example.org'
     354        element[u'name'] = u'The node'
     355        item = disco.DiscoItem.fromElement(element)
     356        self.assertEqual(JID(u'example.org'), item.entity)
     357        self.assertEqual(u'', item.nodeIdentifier)
     358        self.assertEqual(u'The node', item.name)
     359
     360
     361    def test_fromElementNoName(self):
     362        """
     363        Test creating L{disco.DiscoItem} from L{domish.Element}, no name.
     364        """
     365        element = domish.Element((NS_DISCO_ITEMS, u'item'))
     366        element[u'jid'] = u'example.org'
     367        element[u'node'] = u'test'
     368        item = disco.DiscoItem.fromElement(element)
     369        self.assertEqual(JID(u'example.org'), item.entity)
     370        self.assertEqual(u'test', item.nodeIdentifier)
     371        self.assertEqual(None, item.name)
     372
     373    def test_fromElementBadJID(self):
     374        """
     375        Test creating L{disco.DiscoItem} from L{domish.Element}, bad JID.
     376        """
     377        element = domish.Element((NS_DISCO_ITEMS, u'item'))
     378        element[u'jid'] = u'ex@@@ample.org'
     379        item = disco.DiscoItem.fromElement(element)
     380        self.assertIdentical(None, item.entity)
     381
     382
     383
     384class DiscoItemsTest(unittest.TestCase):
     385    """
     386    Tests for L{disco.DiscoItems}.
     387    """
     388
     389    def test_toElement(self):
     390        """
     391        Test C{toElement} creates a correctly namespaced element, no node.
     392        """
     393        items = disco.DiscoItems()
     394        element = items.toElement()
     395
     396        self.assertEqual(NS_DISCO_ITEMS, element.uri)
     397        self.assertEqual(u'query', element.name)
     398        self.assertFalse(element.hasAttribute(u'node'))
     399
     400
     401    def test_toElementNode(self):
     402        """
     403        Test C{toElement} with a node.
     404        """
     405        items = disco.DiscoItems()
     406        items.nodeIdentifier = u'test'
     407        element = items.toElement()
     408
     409        self.assertEqual(u'test', element.getAttribute(u'node'))
     410
     411
     412    def test_toElementChildren(self):
     413        """
     414        Test C{toElement} creates a DOM with proper childs.
     415        """
     416        items = disco.DiscoItems()
     417        items.append(disco.DiscoItem(JID(u'example.org'), u'test', u'A node'))
     418        element = items.toElement()
     419
     420        itemElements = domish.generateElementsQNamed(element.children,
     421                                                     u'item',
     422                                                     NS_DISCO_ITEMS)
     423        self.assertEqual(1, len(list(itemElements)))
     424
     425
     426    def test_fromElement(self):
     427        """
     428        Test creating L{disco.DiscoItems} from L{domish.Element}.
     429        """
     430        xml = """<query xmlns='http://jabber.org/protocol/disco#items'>
     431                   <item jid='example.org' node='test' name='A node'/>
     432                 </query>"""
     433
     434        element = parseXml(xml)
     435        items = disco.DiscoItems.fromElement(element)
     436
     437        items = list(items)
     438        self.assertEqual(1, len(items))
     439        item = items[0]
     440
     441        self.assertEqual(JID(u'example.org'), item.entity)
     442        self.assertEqual(u'test', item.nodeIdentifier)
     443        self.assertEqual(u'A node', item.name)
     444
     445
     446    def test_fromElementNoNode(self):
     447        """
     448        Test creating L{disco.DiscoItems} from L{domish.Element}, no node.
     449        """
     450        xml = """<query xmlns='http://jabber.org/protocol/disco#items'/>"""
     451
     452        element = parseXml(xml)
     453        items = disco.DiscoItems.fromElement(element)
     454
     455        self.assertEqual(u'', items.nodeIdentifier)
     456
     457
     458    def test_fromElementNode(self):
     459        """
     460        Test creating L{disco.DiscoItems} from L{domish.Element}, with node.
     461        """
     462        xml = """<query xmlns='http://jabber.org/protocol/disco#items'
     463                        node='test'>
     464                 </query>"""
     465
     466        element = parseXml(xml)
     467        items = disco.DiscoItems.fromElement(element)
     468
     469        self.assertEqual(u'test', items.nodeIdentifier)
     470
     471
     472
     473class DiscoClientProtocolTest(unittest.TestCase):
     474    """
     475    Tests for L{disco.DiscoClientProtocol}.
     476    """
     477
     478    def setUp(self):
     479        """
     480        Set up stub and protocol for testing.
     481        """
     482        self.stub = XmlStreamStub()
     483        self.protocol = disco.DiscoClientProtocol()
     484        self.protocol.xmlstream = self.stub.xmlstream
     485        self.protocol.connectionInitialized()
     486
     487
     488    def test_requestItems(self):
     489        """
     490        Test request sent out by C{requestItems} and parsing of response.
     491        """
     492        def cb(items):
     493            items = list(items)
     494            self.assertEqual(2, len(items))
     495            self.assertEqual(JID(u'test.example.org'), items[0].entity)
     496
     497        d = self.protocol.requestItems(JID(u'example.org'),u"foo")
     498        d.addCallback(cb)
     499
     500        iq = self.stub.output[-1]
     501        self.assertEqual(u'example.org', iq.getAttribute(u'to'))
     502        self.assertEqual(u'get', iq.getAttribute(u'type'))
     503        self.assertEqual(u'foo', iq.query.getAttribute(u'node'))
     504        self.assertEqual(NS_DISCO_ITEMS, iq.query.uri)
     505
     506        response = toResponse(iq, u'result')
     507        query = response.addElement((NS_DISCO_ITEMS, u'query'))
     508
     509        element = query.addElement(u'item')
     510        element[u'jid'] = u'test.example.org'
     511        element[u'node'] = u'music'
     512        element[u'name'] = u'Music from the time of Shakespeare'
     513
     514        element = query.addElement(u'item')
     515        element[u'jid'] = u"test2.example.org"
     516
     517        self.stub.send(response)
     518        return d
     519
     520
     521    def test_requestInfo(self):
     522        """
     523        Test request sent out by C{requestInfo} and parsing of response.
     524        """
     525        def cb(info):
     526            self.assertIn((u'conference', u'text'), info.identities)
     527            self.assertIn(u'http://jabber.org/protocol/disco#info',
     528                          info.features)
     529            self.assertIn(u'http://jabber.org/protocol/muc',
     530                          info.features)
     531
     532        d = self.protocol.requestInfo(JID(u'example.org'),'foo')
     533        d.addCallback(cb)
     534
     535        iq = self.stub.output[-1]
     536        self.assertEqual(u'example.org', iq.getAttribute(u'to'))
     537        self.assertEqual(u'get', iq.getAttribute(u'type'))
     538        self.assertEqual(u'foo', iq.query.getAttribute(u'node'))
     539        self.assertEqual(NS_DISCO_INFO, iq.query.uri)
     540
     541        response = toResponse(iq, u'result')
     542        query = response.addElement((NS_DISCO_INFO, u'query'))
     543
     544        element = query.addElement(u"identity")
     545        element[u'category'] = u'conference' # required
     546        element[u'type'] = u'text' # required
     547        element[u"name"] = u'Romeo and Juliet, Act II, Scene II' # optional
     548
     549        element = query.addElement("feature")
     550        element[u'var'] = u'http://jabber.org/protocol/disco#info' # required
     551
     552        element = query.addElement(u"feature")
     553        element[u'var'] = u'http://jabber.org/protocol/muc'
     554
     555        self.stub.send(response)
     556        return d
     557
    20558
    21559
     
    67605        return d
    68606
     607
    69608    def test_onDiscoInfoWithNode(self):
    70609        """
     
    128667        return d
    129668
     669
    130670    def test_onDiscoItemsWithNode(self):
    131671        """
Note: See TracChangeset for help on using the changeset viewer.