Ignore:
Files:
11 added
32 edited

Legend:

Unmodified
Added
Removed
  • LICENSE

    r53 r96  
    1 Copyright (c) 2003-2009 Ralph Meijer
     1Copyright (c) 2003-2011 Ralph Meijer.
    22
    33Permission is hereby granted, free of charge, to any person obtaining
  • README

    r73 r96  
    3131======================
    3232
    33 The code in this distribution is Copyright (c) 2003-2009 Ralph Meijer, unless
    34 excplicitely specified otherwise.
     33The code in this distribution is Copyright (c) Ralph Meijer, unless
     34explicitly specified otherwise.
    3535
    3636Wokkel is made available under the MIT License. The included LICENSE file
  • setup.py

    • Property exe set to *
    r73 r96  
    11#!/usr/bin/env python
    22
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    1818          'wokkel',
    1919          'wokkel.test',
     20          'twisted.plugins',
    2021      ],
     22      package_data={'twisted.plugins': ['twisted/plugins/server.py']},
    2123)
  • wokkel/__init__.py

    r1 r96  
    1 # Copyright (c) 2003-2007 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details
    33
  • wokkel/client.py

    • Property exe set to *
    r73 r96  
    11# -*- test-case-name: wokkel.test.test_client -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
  • wokkel/compat.py

    • Property exe set to *
    r63 r96  
    11# -*- test-case-name: wokkel.test.test_compat -*-
    22#
    3 # Copyright (c) 2001-2009 Twisted Matrix Laboratories.
     3# Copyright (c) Twisted Matrix Laboratories.
    44# See LICENSE for details.
    55
  • wokkel/component.py

    • Property exe set to *
    r54 r96  
    11# -*- test-case-name: wokkel.test.test_component -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    158158
    159159    @ivar secret: The shared used to authorized incoming component connections.
    160     @type secret: C{str}.
     160    @type secret: C{unicode}.
    161161    """
    162162
     
    237237        be exchanged.
    238238        """
    239         calculatedHash = xmlstream.hashPassword(self.xmlstream.sid, self.secret)
     239        calculatedHash = xmlstream.hashPassword(self.xmlstream.sid,
     240                                                unicode(self.secret))
    240241        if handshake != calculatedHash:
    241242            exc = error.StreamError('not-authorized', text='Invalid hash')
  • wokkel/data_form.py

    • Property exe set to *
    r56 r96  
    11# -*- test-case-name: wokkel.test.test_data_form -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    7676            option['label'] = self.label
    7777        return option
     78
    7879
    7980    @staticmethod
     
    130131        self.fieldType = fieldType
    131132        self.var = var
     133
    132134        if value is not None:
    133135            self.value = value
    134136        else:
    135137            self.values = values or []
     138
     139        self.label = label
    136140
    137141        try:
     
    141145            self.options = options or []
    142146
    143         self.label = label
    144147        self.desc = desc
    145148        self.required = required
     
    213216        if self.values:
    214217            if (self.fieldType not in ('hidden', 'jid-multi', 'list-multi',
    215                                  'text-multi') and
     218                                       'text-multi', None) and
    216219                len(self.values) > 1):
    217220                raise TooManyValuesError()
     
    234237            self.values = newValues
    235238
     239
    236240    def toElement(self, asForm=False):
    237241        """
     
    245249        field = domish.Element((NS_X_DATA, 'field'))
    246250
    247         if asForm or self.fieldType != 'text-single':
     251        if self.fieldType:
    248252            field['type'] = self.fieldType
    249253
     
    252256
    253257        for value in self.values:
    254             if self.fieldType == 'boolean':
     258            if isinstance(value, bool):
    255259                value = unicode(value).lower()
    256             elif self.fieldType in ('jid-single', 'jid-multi'):
    257                 value = value.full()
     260            else:
     261                value = unicode(value)
    258262
    259263            field.addElement('value', content=value)
     
    323327
    324328    @staticmethod
    325     def fromDict(dictionary):
    326         kwargs = dictionary.copy()
    327 
    328         if 'type' in dictionary:
    329             kwargs['fieldType'] = dictionary['type']
     329    def fromDict(fieldDict):
     330        """
     331        Create a field from a dictionary.
     332
     333        This is a short hand for passing arguments directly on Field object
     334        creation. The field type is represented by the C{'type'} key. For
     335        C{'options'} the value is not a list of L{Option}s, but a dictionary
     336        keyed by value, with an optional label as value.
     337        """
     338        kwargs = fieldDict.copy()
     339
     340        if 'type' in fieldDict:
     341            kwargs['fieldType'] = fieldDict['type']
    330342            del kwargs['type']
    331343
    332         if 'options' in dictionary:
     344        if 'options' in fieldDict:
    333345            options = []
    334             for value, label in dictionary['options'].iteritems():
     346            for value, label in fieldDict['options'].iteritems():
    335347                options.append(Option(value, label))
    336348            kwargs['options'] = options
     
    344356    Data Form.
    345357
    346     There are two similarly named properties of forms. The L{formType} is the
     358    There are two similarly named properties of forms. The C{formType} is the
    347359    the so-called type of the form, and is set as the C{'type'} attribute
    348360    on the form's root element.
     
    352364    special hidden field named C{'FORM_TYPE'}, to put the names of all
    353365    other fields in the namespace of the value of that field. This namespace
    354     is recorded in the L{formNamespace} instance variable.
     366    is recorded in the C{formNamespace} instance variable.
    355367
    356368    @ivar formType: Type of form. One of C{'form'}, C{'submit'}, {'cancel'},
    357369                    or {'result'}.
    358     @type formType: C{str}.
     370    @type formType: C{str}
     371
     372    @ivar title: Natural language title of the form.
     373    @type title: C{unicode}
     374
     375    @ivar instructions: Natural language instructions as a list of C{unicode}
     376        strings without line breaks.
     377    @type instructions: C{list}
     378
    359379    @ivar formNamespace: The optional namespace of the field names for this
    360                          form. This goes in the special field named
    361                          C{'FORM_TYPE'}, if set.
     380        form. This goes in the special field named C{'FORM_TYPE'}, if set.
    362381    @type formNamespace: C{str}.
    363     @ivar fields: Dictionary of fields that have a name. Note that this is
    364                   meant to be used for reading, only. One should use
    365                   L{addField} for adding fields.
     382
     383    @ivar fields: Dictionary of named fields. Note that this is meant to be
     384        used for reading, only. One should use L{addField} or L{makeFields} and
     385        L{removeField} for adding and removing fields.
    366386    @type fields: C{dict}
     387
     388    @ivar fieldList: List of all fields, in the order they are added. Like
     389        C{fields}, this is meant to be used for reading, only.
     390    @type fieldList: C{list}
    367391    """
    368392
     
    393417            r.append(", formNamespace=")
    394418            r.append(repr(self.formNamespace))
    395         if self.fields:
     419        if self.fieldList:
    396420            r.append(", fields=")
    397421            r.append(repr(self.fieldList))
     
    404428        Add a field to this form.
    405429
    406         Fields are added in order, and L{fields} is a dictionary of the
     430        Fields are added in order, and C{fields} is a dictionary of the
    407431        named fields, that is kept in sync only if this method is used for
    408432        adding new fields. Multiple fields with the same name are disallowed.
     
    417441
    418442
     443    def removeField(self, field):
     444        """
     445        Remove a field from this form.
     446        """
     447        self.fieldList.remove(field)
     448
     449        if field.var is not None:
     450            del self.fields[field.var]
     451
     452
     453    def makeFields(self, values, fieldDefs=None, filterUnknown=True):
     454        """
     455        Create fields from values and add them to this form.
     456
     457        This creates fields from a mapping of name to value(s) and adds them to
     458        this form. It is typically used for generating outgoing forms.
     459
     460        If C{fieldDefs} is not C{None}, this is used to fill in
     461        additional properties of fields, like the field types, labels and
     462        possible options.
     463
     464        If C{filterUnknown} is C{True} and C{fieldDefs} is not C{None}, fields
     465        will only be created from C{values} with a corresponding entry in
     466        C{fieldDefs}.
     467
     468        If the field type is unknown, the field type is C{None}. When the form
     469        is rendered using L{toElement}, these fields will have no C{'type'}
     470        attribute, and it is up to the receiving party to interpret the values
     471        properly (e.g. by knowing about the FORM_TYPE in L{formNamespace} and
     472        the field name).
     473
     474        @param values: Values to create fields from.
     475        @type values: C{dict}
     476
     477        @param fieldDefs: Field definitions as a dictionary. See
     478            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     479        @type fieldDefs: C{dict}
     480
     481        @param filterUnknown: If C{True}, ignore fields that are not in
     482            C{fieldDefs}.
     483        @type filterUnknown: C{bool}
     484        """
     485        for name, value in values.iteritems():
     486            fieldDict = {'var': name,
     487                         'type': None}
     488
     489            if fieldDefs is not None:
     490                if name in fieldDefs:
     491                    fieldDict.update(fieldDefs[name])
     492                elif filterUnknown:
     493                    continue
     494
     495            if isinstance(value, list):
     496                fieldDict['values'] = value
     497            else:
     498                fieldDict['value'] = value
     499
     500            self.addField(Field.fromDict(fieldDict))
     501
     502
    419503    def toElement(self):
     504        """
     505        Return the DOM representation of this Form.
     506
     507        @rtype: L{domish.Element}
     508        """
    420509        form = domish.Element((NS_X_DATA, 'x'))
    421510        form['type'] = self.formType
     
    425514
    426515        for instruction in self.instructions:
    427             form.addElement('instruction', content=instruction)
     516            form.addElement('instructions', content=instruction)
    428517
    429518        if self.formNamespace is not None:
     
    478567        return form
    479568
     569
    480570    def getValues(self):
     571        """
     572        Extract values from the named form fields.
     573
     574        For all named fields, the corresponding value or values are
     575        returned in a dictionary keyed by the field name. For multi-value
     576        fields, the dictionary value is a list, otherwise a single value.
     577
     578        If a field has no type, and the field has multiple values, the value of
     579        the dictionary entry is the list of values. Otherwise, it will be a
     580        single value.
     581
     582        @rtype: C{dict}
     583        """
    481584        values = {}
    482585
    483586        for name, field in self.fields.iteritems():
    484             if len(field.values) > 1:
     587            if (field.fieldType in ('jid-multi', 'list-multi', 'text-multi') or
     588                (field.fieldType is None and len(field.values) > 1)):
    485589                value = field.values
    486590            else:
     
    490594
    491595        return values
     596
     597
     598    def typeCheck(self, fieldDefs=None, filterUnknown=False):
     599        """
     600        Check values of fields according to the field definition.
     601
     602        This method walks all named fields to check their values against their
     603        type, and is typically used for forms received from other entities. The
     604        field definition in C{fieldDefs} is used to check the field type.
     605
     606        If C{filterUnknown} is C{True}, fields that are not present in
     607        C{fieldDefs} are removed from the form.
     608
     609        If the field type is C{None} (when not set by the sending entity),
     610        the type from the field definitition is used, or C{'text-single'} if
     611        that is not set.
     612
     613        If C{fieldDefs} is None, an empty dictionary is assumed. This is
     614        useful for coercing boolean and JID values on forms with type
     615        C{'form'}.
     616
     617        @param fieldDefs: Field definitions as a dictionary. See
     618            L{wokkel.iwokkel.IPubSubService.getConfigurationOptions}
     619        @type fieldDefs: C{dict}
     620
     621        @param filterUnknown: If C{True}, remove fields that are not in
     622            C{fieldDefs}.
     623        @type filterUnknown: C{bool}
     624        """
     625
     626        if fieldDefs is None:
     627            fieldDefs = {}
     628
     629        filtered = []
     630
     631        for name, field in self.fields.iteritems():
     632            if name in fieldDefs:
     633                fieldDef = fieldDefs[name]
     634                if 'type' not in fieldDef:
     635                    fieldDef['type'] = 'text-single'
     636
     637                if field.fieldType is None:
     638                    field.fieldType = fieldDef['type']
     639                elif field.fieldType != fieldDef['type']:
     640                    raise TypeError("Field type for %r is %r, expected %r" %
     641                                    (name,
     642                                     field.fieldType,
     643                                     fieldDef['type']))
     644                else:
     645                    # Field type is correct
     646                    pass
     647                field.typeCheck()
     648            elif filterUnknown:
     649                filtered.append(field)
     650            elif field.fieldType is not None:
     651                field.typeCheck()
     652            else:
     653                # Unknown field without type, no checking, no filtering
     654                pass
     655
     656        for field in filtered:
     657            self.removeField(field)
     658
     659
     660
     661def findForm(element, formNamespace):
     662    """
     663    Find a Data Form.
     664
     665    Look for an element that represents a Data Form with the specified
     666    form namespace as a child element of the given element.
     667    """
     668    if not element:
     669        return None
     670
     671    for child in element.elements():
     672        if (child.uri, child.name) == ((NS_X_DATA, 'x')):
     673            form = Form.fromElement(child)
     674
     675            if (form.formNamespace == formNamespace or
     676                not form.formNamespace and form.formType=='cancel'):
     677                return form
     678
     679    return None
  • wokkel/disco.py

    • Property exe set to *
    r133 r134  
    11# -*- test-case-name: wokkel.test.test_disco -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    1515from twisted.words.xish import domish
    1616
    17 from wokkel import data_form
    18 from wokkel.compat import IQ
     17from wokkel import data_form, generic
    1918from wokkel.iwokkel import IDisco
    2019from wokkel.subprotocols import IQHandlerMixin, XMPPHandler
     
    120119    @type nodeIdentifier: C{unicode}
    121120    @ivar features: Features as L{DiscoFeature}.
    122     @type features: C{set)
     121    @type features: C{set}
    123122    @ivar identities: Identities as a mapping from (category, type) to name,
    124123                      all C{unicode}.
     
    347346
    348347
    349 class _DiscoRequest(IQ):
    350     """
    351     Element representing an XMPP service discovery request.
    352     """
    353 
    354     def __init__(self, xs, namespace, nodeIdentifier=''):
    355         """
    356         Initialize the request.
    357 
    358         @param xs: XML Stream the request should go out on.
    359         @type xs: L{xmlstream.XmlStream}
    360         @param namespace: Request namespace.
    361         @type namespace: C{str}
    362         @param nodeIdentifier: Node to request info from.
    363         @type nodeIdentifier: C{unicode}
    364         """
    365         IQ.__init__(self, xs, "get")
    366         query = self.addElement((namespace, 'query'))
    367         if nodeIdentifier:
    368             query['node'] = nodeIdentifier
     348class _DiscoRequest(generic.Request):
     349    """
     350    A Service Discovery request.
     351
     352    @ivar verb: Type of request: C{'info'} or C{'items'}.
     353    @type verb: C{str}
     354    @ivar nodeIdentifier: Optional node to request info for.
     355    @type nodeIdentifier: C{unicode}
     356    """
     357
     358    verb = None
     359    nodeIdentifier = ''
     360
     361    _requestVerbMap = {
     362            NS_DISCO_INFO: 'info',
     363            NS_DISCO_ITEMS: 'items',
     364            }
     365
     366    _verbRequestMap = dict(((v, k) for k, v in _requestVerbMap.iteritems()))
     367
     368    def __init__(self, verb=None, nodeIdentifier='',
     369                       recipient=None, sender=None):
     370        generic.Request.__init__(self, recipient=recipient, sender=sender,
     371                                       stanzaType='get')
     372        self.verb = verb
     373        self.nodeIdentifier = nodeIdentifier
     374
     375
     376    def parseElement(self, element):
     377        generic.Request.parseElement(self, element)
     378
     379        verbElement = None
     380        for child in element.elements():
     381            if child.name == 'query' and child.uri in self._requestVerbMap:
     382                self.verb = self._requestVerbMap[child.uri]
     383                verbElement = child
     384
     385        if verbElement:
     386            self.nodeIdentifier = verbElement.getAttribute('node', '')
     387
     388
     389    def toElement(self):
     390        element = generic.Request.toElement(self)
     391
     392        childURI = self._verbRequestMap[self.verb]
     393        query = element.addElement((childURI, 'query'))
     394
     395        if self.nodeIdentifier:
     396            query['node'] = self.nodeIdentifier
     397
     398        return element
    369399
    370400
     
    389419        """
    390420
    391         request = _DiscoRequest(self.xmlstream, NS_DISCO_INFO, nodeIdentifier)
    392         if sender is not None:
    393             request['from'] = unicode(sender)
    394 
    395         d = request.send(entity.full())
     421        request = _DiscoRequest('info', nodeIdentifier)
     422        request.sender = sender
     423        request.recipient = entity
     424
     425        d = self.request(request)
    396426        d.addCallback(lambda iq: DiscoInfo.fromElement(iq.query))
    397427        return d
     
    412442        """
    413443
    414         request = _DiscoRequest(self.xmlstream, NS_DISCO_ITEMS, nodeIdentifier)
    415         if sender is not None:
    416             request['from'] = unicode(sender)
    417 
    418         d = request.send(entity.full())
     444        request = _DiscoRequest('items', nodeIdentifier)
     445        request.sender = sender
     446        request.recipient = entity
     447
     448        d = self.request(request)
    419449        d.addCallback(lambda iq: DiscoItems.fromElement(iq.query))
    420450        return d
     
    446476        @type iq: L{Element<twisted.words.xish.domish.Element>}
    447477        """
    448         requestor = jid.internJID(iq["from"])
    449         target = jid.internJID(iq["to"])
    450         nodeIdentifier = iq.query.getAttribute("node", '')
     478        request = _DiscoRequest.fromElement(iq)
    451479
    452480        def toResponse(info):
    453             if nodeIdentifier and not info:
     481            if request.nodeIdentifier and not info:
    454482                raise error.StanzaError('item-not-found')
    455483            else:
    456484                response = DiscoInfo()
    457                 response.nodeIdentifier = nodeIdentifier
     485                response.nodeIdentifier = request.nodeIdentifier
    458486
    459487                for item in info:
     
    462490            return response.toElement()
    463491
    464         d = self.info(requestor, target, nodeIdentifier)
     492        d = self.info(request.sender, request.recipient,
     493                      request.nodeIdentifier)
    465494        d.addCallback(toResponse)
    466495        return d
     
    474503        @type iq: L{Element<twisted.words.xish.domish.Element>}
    475504        """
    476         requestor = jid.internJID(iq["from"])
    477         target = jid.internJID(iq["to"])
    478         nodeIdentifier = iq.query.getAttribute("node", '')
     505        request = _DiscoRequest.fromElement(iq)
    479506
    480507        def toResponse(items):
    481508            response = DiscoItems()
    482             response.nodeIdentifier = nodeIdentifier
     509            response.nodeIdentifier = request.nodeIdentifier
    483510
    484511            for item in items:
     
    487514            return response.toElement()
    488515
    489         d = self.items(requestor, target, nodeIdentifier)
     516        d = self.items(request.sender, request.recipient,
     517                       request.nodeIdentifier)
    490518        d.addCallback(toResponse)
    491519        return d
  • wokkel/formats.py

    • Property exe set to *
    r12 r96  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
  • wokkel/generic.py

    • Property exe set to *
    r68 r103  
    11# -*- test-case-name: wokkel.test.test_generic -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    2121    from wokkel.compat import BootstrapMixin
    2222
    23 from wokkel import disco
    2423from wokkel.iwokkel import IDisco
    2524from wokkel.subprotocols import XMPPHandler
     
    121120
    122121        if not node:
     122            from wokkel import disco
    123123            info.add(disco.DiscoFeature(NS_VERSION))
    124124
     
    175175    """
    176176
     177    recipient = None
     178    sender = None
    177179    stanzaKind = None
    178180    stanzaID = None
     
    237239
    238240
     241class Request(Stanza):
     242    """
     243    IQ request stanza.
     244
     245    This is a base class for IQ get or set stanzas, to be used with
     246    L{wokkel.subprotocols.StreamManager.request}.
     247    """
     248
     249    stanzaKind = 'iq'
     250    stanzaType = 'get'
     251    timeout = None
     252
     253    def __init__(self, recipient=None, sender=None, stanzaType='get'):
     254        Stanza.__init__(self, recipient=recipient, sender=sender)
     255        self.stanzaType = stanzaType
     256
     257
     258    def toElement(self):
     259        element = Stanza.toElement(self)
     260
     261        if not self.stanzaID:
     262            element.addUniqueId()
     263            self.stanzaID = element['id']
     264
     265        return element
     266
     267
     268
    239269class DeferredXmlStreamFactory(BootstrapMixin, protocol.ClientFactory):
    240270    protocol = xmlstream.XmlStream
  • wokkel/iwokkel.py

    • Property exe set to *
  • wokkel/ping.py

    • Property exe set to *
    r65 r96  
    11# -*- test-case-name: wokkel.test.test_ping -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    9595        response = toResponse(iq, 'result')
    9696        self.xmlstream.send(response)
     97        iq.handled = True
    9798
    9899
  • wokkel/pubsub.py

    • Property exe set to *
    r63 r97  
    11# -*- test-case-name: wokkel.test.test_pubsub -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
     
    103103    A subscription to a node.
    104104
    105     @ivar nodeIdentifier: The identifier of the node subscribed to.
    106                           The root node is denoted by C{None}.
     105    @ivar nodeIdentifier: The identifier of the node subscribed to.  The root
     106        node is denoted by C{None}.
     107    @type nodeIdentifier: C{unicode}
     108
    107109    @ivar subscriber: The subscribing entity.
     110    @type subscriber: L{jid.JID}
     111
    108112    @ivar state: The subscription state. One of C{'subscribed'}, C{'pending'},
    109113                 C{'unconfigured'}.
     114    @type state: C{unicode}
     115
    110116    @ivar options: Optional list of subscription options.
    111     @type options: C{dict}.
    112     """
    113 
    114     def __init__(self, nodeIdentifier, subscriber, state, options=None):
     117    @type options: C{dict}
     118
     119    @ivar subscriptionIdentifier: Optional subscription identifier.
     120    @type subscriptionIdentifier: C{unicode}
     121    """
     122
     123    def __init__(self, nodeIdentifier, subscriber, state, options=None,
     124                       subscriptionIdentifier=None):
    115125        self.nodeIdentifier = nodeIdentifier
    116126        self.subscriber = subscriber
    117127        self.state = state
    118128        self.options = options or {}
     129        self.subscriptionIdentifier = subscriptionIdentifier
     130
     131
     132    @staticmethod
     133    def fromElement(element):
     134        return Subscription(
     135                element.getAttribute('node'),
     136                jid.JID(element.getAttribute('jid')),
     137                element.getAttribute('subscription'),
     138                subscriptionIdentifier=element.getAttribute('subid'))
     139
     140
     141    def toElement(self, defaultUri=None):
     142        """
     143        Return the DOM representation of this subscription.
     144
     145        @rtype: L{domish.Element}
     146        """
     147        element = domish.Element((defaultUri, 'subscription'))
     148        if self.nodeIdentifier:
     149            element['node'] = self.nodeIdentifier
     150        element['jid'] = unicode(self.subscriber)
     151        element['subscription'] = self.state
     152        if self.subscriptionIdentifier:
     153            element['subid'] = self.subscriptionIdentifier
     154        return element
    119155
    120156
     
    139175        """
    140176
    141         domish.Element.__init__(self, (NS_PUBSUB, 'item'))
     177        domish.Element.__init__(self, (None, 'item'))
    142178        if id is not None:
    143179            self['id'] = id
     
    185221                         L{Subscription}.
    186222    @type subscriptions: C{set}
     223    @ivar affiliations: Affiliations to be modified, as a dictionary of entity
     224                        (L{JID} to affiliation (C{unicode}).
     225    @type affiliations: C{dict}
    187226    """
    188227
     
    199238    subscriptionIdentifier = None
    200239    subscriptions = None
     240    affiliations = None
    201241
    202242    # Map request iq type and subelement name to request verb
     
    229269    _parameters = {
    230270        'publish': ['node', 'items'],
    231         'subscribe': ['nodeOrEmpty', 'jid'],
    232         'unsubscribe': ['nodeOrEmpty', 'jid'],
    233         'optionsGet': ['nodeOrEmpty', 'jid'],
    234         'optionsSet': ['nodeOrEmpty', 'jid', 'options'],
     271        'subscribe': ['nodeOrEmpty', 'jid', 'optionsWithSubscribe'],
     272        'unsubscribe': ['nodeOrEmpty', 'jid', 'subidOrNone'],
     273        'optionsGet': ['nodeOrEmpty', 'jid', 'subidOrNone'],
     274        'optionsSet': ['nodeOrEmpty', 'jid', 'options', 'subidOrNone'],
    235275        'subscriptions': [],
    236276        'affiliations': [],
    237         'create': ['nodeOrNone'],
     277        'create': ['nodeOrNone', 'configureOrNone'],
    238278        'default': ['default'],
    239279        'configureGet': ['nodeOrEmpty'],
    240280        'configureSet': ['nodeOrEmpty', 'configure'],
    241         'items': ['node', 'maxItems', 'itemIdentifiers'],
     281        'items': ['node', 'maxItems', 'itemIdentifiers', 'subidOrNone'],
    242282        'retract': ['node', 'itemIdentifiers'],
    243283        'purge': ['node'],
    244284        'delete': ['node'],
    245285        'affiliationsGet': ['nodeOrEmpty'],
    246         'affiliationsSet': [],
     286        'affiliationsSet': ['nodeOrEmpty', 'affiliations'],
    247287        'subscriptionsGet': ['nodeOrEmpty'],
    248288        'subscriptionsSet': [],
     
    251291    def __init__(self, verb=None):
    252292        self.verb = verb
    253 
    254 
    255     @staticmethod
    256     def _findForm(element, formNamespace):
    257         """
    258         Find a Data Form.
    259 
    260         Look for an element that represents a Data Form with the specified
    261         form namespace as a child element of the given element.
    262         """
    263         if not element:
    264             return None
    265 
    266         form = None
    267         for child in element.elements():
    268             try:
    269                 form = data_form.Form.fromElement(child)
    270             except data_form.Error:
    271                 continue
    272 
    273             if form.formNamespace != NS_PUBSUB_NODE_CONFIG:
    274                 continue
    275 
    276         return form
    277293
    278294
     
    343359        if self.items:
    344360            for item in self.items:
     361                item.uri = NS_PUBSUB
    345362                verbElement.addChild(item)
    346363
     
    367384        Parse node type out of a request for the default node configuration.
    368385        """
    369         form = PubSubRequest._findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
     386        form = data_form.findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
    370387        if form and form.formType == 'submit':
    371388            values = form.getValues()
     
    379396        Parse options out of a request for setting the node configuration.
    380397        """
    381         form = PubSubRequest._findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
     398        form = data_form.findForm(verbElement, NS_PUBSUB_NODE_CONFIG)
    382399        if form:
    383             if form.formType == 'submit':
    384                 self.options = form.getValues()
    385             elif form.formType == 'cancel':
    386                 self.options = {}
     400            if form.formType in ('submit', 'cancel'):
     401                self.options = form
    387402            else:
    388                 raise BadRequest(text="Unexpected form type %r" % form.formType)
     403                raise BadRequest(text=u"Unexpected form type '%s'" % form.formType)
    389404        else:
    390405            raise BadRequest(text="Missing configuration form")
    391406
     407
     408    def _parse_configureOrNone(self, verbElement):
     409        """
     410        Parse optional node configuration form in create request.
     411        """
     412        for element in verbElement.parent.elements():
     413            if element.uri == NS_PUBSUB and element.name == 'configure':
     414                form = data_form.findForm(element, NS_PUBSUB_NODE_CONFIG)
     415                if form:
     416                    if form.formType != 'submit':
     417                        raise BadRequest(text=u"Unexpected form type '%s'" %
     418                                              form.formType)
     419                else:
     420                    form = data_form.Form('submit',
     421                                          formNamespace=NS_PUBSUB_NODE_CONFIG)
     422                self.options = form
     423
     424
     425    def _render_configureOrNone(self, verbElement):
     426        """
     427        Render optional node configuration form in create request.
     428        """
     429        if self.options is not None:
     430            configure = verbElement.parent.addElement('configure')
     431            configure.addChild(self.options.toElement())
    392432
    393433
     
    431471    def _render_maxItems(self, verbElement):
    432472        """
    433         Parse maximum items into an items request.
     473        Render maximum items into an items request.
    434474        """
    435475        if self.maxItems:
     
    437477
    438478
     479    def _parse_subidOrNone(self, verbElement):
     480        """
     481        Parse subscription identifier out of a request.
     482        """
     483        self.subscriptionIdentifier = verbElement.getAttribute("subid")
     484
     485
     486    def _render_subidOrNone(self, verbElement):
     487        """
     488        Render subscription identifier into a request.
     489        """
     490        if self.subscriptionIdentifier:
     491            verbElement['subid'] = self.subscriptionIdentifier
     492
     493
    439494    def _parse_options(self, verbElement):
    440         form = PubSubRequest._findForm(verbElement, NS_PUBSUB_SUBSCRIBE_OPTIONS)
     495        """
     496        Parse options form out of a subscription options request.
     497        """
     498        form = data_form.findForm(verbElement, NS_PUBSUB_SUBSCRIBE_OPTIONS)
    441499        if form:
    442             if form.formType == 'submit':
    443                 self.options = form.getValues()
    444             elif form.formType == 'cancel':
    445                 self.options = {}
     500            if form.formType in ('submit', 'cancel'):
     501                self.options = form
    446502            else:
    447                 raise BadRequest(text="Unexpected form type %r" % form.formType)
     503                raise BadRequest(text=u"Unexpected form type '%s'" % form.formType)
    448504        else:
    449505            raise BadRequest(text="Missing options form")
    450506
     507
     508
     509    def _render_options(self, verbElement):
     510        verbElement.addChild(self.options.toElement())
     511
     512
     513    def _parse_optionsWithSubscribe(self, verbElement):
     514        for element in verbElement.parent.elements():
     515            if element.name == 'options' and element.uri == NS_PUBSUB:
     516                form = data_form.findForm(element,
     517                                          NS_PUBSUB_SUBSCRIBE_OPTIONS)
     518                if form:
     519                    if form.formType != 'submit':
     520                        raise BadRequest(text=u"Unexpected form type '%s'" %
     521                                              form.formType)
     522                else:
     523                    form = data_form.Form('submit',
     524                                          formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     525                self.options = form
     526
     527
     528    def _render_optionsWithSubscribe(self, verbElement):
     529        if self.options:
     530            optionsElement = verbElement.parent.addElement('options')
     531            self._render_options(optionsElement)
     532
     533
     534    def _parse_affiliations(self, verbElement):
     535        self.affiliations = {}
     536        for element in verbElement.elements():
     537            if (element.uri == NS_PUBSUB_OWNER and
     538                element.name == 'affiliation'):
     539                try:
     540                    entity = jid.internJID(element['jid']).userhostJID()
     541                except KeyError:
     542                    raise BadRequest(text='Missing jid attribute')
     543
     544                if entity in self.affiliations:
     545                    raise BadRequest(text='Multiple affiliations for an entity')
     546
     547                try:
     548                    affiliation = element['affiliation']
     549                except KeyError:
     550                    raise BadRequest(text='Missing affiliation attribute')
     551
     552                self.affiliations[entity] = affiliation
     553
     554
    451555    def parseElement(self, element):
    452556        """
     
    455559        generic.Stanza.parseElement(self, element)
    456560
     561        verbs = []
     562        verbElements = []
    457563        for child in element.pubsub.elements():
    458564            key = (self.stanzaType, child.uri, child.name)
     
    461567            except KeyError:
    462568                continue
     569
     570            verbs.append(verb)
     571            verbElements.append(child)
     572
     573        if not verbs:
     574            raise NotImplementedError()
     575
     576        if len(verbs) > 1:
     577            if 'optionsSet' in verbs and 'subscribe' in verbs:
     578                self.verb = 'subscribe'
     579                verbElement = verbElements[verbs.index('subscribe')]
    463580            else:
    464                 self.verb = verb
    465                 break
    466 
    467         if not self.verb:
    468             raise NotImplementedError()
    469 
    470         for parameter in self._parameters[verb]:
    471             getattr(self, '_parse_%s' % parameter)(child)
     581                raise NotImplementedError()
     582        else:
     583            self.verb = verbs[0]
     584            verbElement = verbElements[0]
     585
     586        for parameter in self._parameters[self.verb]:
     587            getattr(self, '_parse_%s' % parameter)(verbElement)
     588
    472589
    473590
     
    574691
    575692    def _onEvent(self, message):
     693        if message.getAttribute('type') == 'error':
     694            return
     695
    576696        try:
    577697            sender = jid.JID(message["from"])
     
    632752
    633753
    634     def createNode(self, service, nodeIdentifier=None, sender=None):
     754    def createNode(self, service, nodeIdentifier=None, options=None,
     755                         sender=None):
    635756        """
    636757        Create a publish subscribe node.
     
    640761        @param nodeIdentifier: Optional suggestion for the id of the node.
    641762        @type nodeIdentifier: C{unicode}
     763        @param options: Optional node configuration options.
     764        @type options: C{dict}
    642765        """
    643766        request = PubSubRequest('create')
     
    645768        request.nodeIdentifier = nodeIdentifier
    646769        request.sender = sender
     770
     771        if options:
     772            form = data_form.Form(formType='submit',
     773                                  formNamespace=NS_PUBSUB_NODE_CONFIG)
     774            form.makeFields(options)
     775            request.options = form
    647776
    648777        def cb(iq):
     
    675804
    676805
    677     def subscribe(self, service, nodeIdentifier, subscriber, sender=None):
     806    def subscribe(self, service, nodeIdentifier, subscriber,
     807                        options=None, sender=None):
    678808        """
    679809        Subscribe to a publish subscribe node.
     
    681811        @param service: The publish subscribe service that keeps the node.
    682812        @type service: L{JID}
     813
    683814        @param nodeIdentifier: The identifier of the node.
    684815        @type nodeIdentifier: C{unicode}
     816
    685817        @param subscriber: The entity to subscribe to the node. This entity
    686                            will get notifications of new published items.
     818            will get notifications of new published items.
    687819        @type subscriber: L{JID}
     820
     821        @param options: Subscription options.
     822        @type options: C{dict}
     823
     824        @return: Deferred that fires with L{Subscription} or errbacks with
     825            L{SubscriptionPending} or L{SubscriptionUnconfigured}.
     826        @rtype: L{defer.Deferred}
    688827        """
    689828        request = PubSubRequest('subscribe')
     
    693832        request.sender = sender
    694833
     834        if options:
     835            form = data_form.Form(formType='submit',
     836                                  formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     837            form.makeFields(options)
     838            request.options = form
     839
    695840        def cb(iq):
    696             subscription = iq.pubsub.subscription["subscription"]
    697 
    698             if subscription == 'pending':
    699                 raise SubscriptionPending
    700             elif subscription == 'unconfigured':
    701                 raise SubscriptionUnconfigured
     841            subscription = Subscription.fromElement(iq.pubsub.subscription)
     842
     843            if subscription.state == 'pending':
     844                raise SubscriptionPending()
     845            elif subscription.state == 'unconfigured':
     846                raise SubscriptionUnconfigured()
    702847            else:
    703848                # we assume subscription == 'subscribed'
    704849                # any other value would be invalid, but that should have
    705850                # yielded a stanza error.
    706                 return None
     851                return subscription
    707852
    708853        d = request.send(self.xmlstream)
     
    711856
    712857
    713     def unsubscribe(self, service, nodeIdentifier, subscriber, sender=None):
     858    def unsubscribe(self, service, nodeIdentifier, subscriber,
     859                          subscriptionIdentifier=None, sender=None):
    714860        """
    715861        Unsubscribe from a publish subscribe node.
     
    717863        @param service: The publish subscribe service that keeps the node.
    718864        @type service: L{JID}
     865
    719866        @param nodeIdentifier: The identifier of the node.
    720867        @type nodeIdentifier: C{unicode}
     868
    721869        @param subscriber: The entity to unsubscribe from the node.
    722870        @type subscriber: L{JID}
     871
     872        @param subscriptionIdentifier: Optional subscription identifier.
     873        @type subscriptionIdentifier: C{unicode}
    723874        """
    724875        request = PubSubRequest('unsubscribe')
     
    726877        request.nodeIdentifier = nodeIdentifier
    727878        request.subscriber = subscriber
     879        request.subscriptionIdentifier = subscriptionIdentifier
    728880        request.sender = sender
    729881        return request.send(self.xmlstream)
     
    749901
    750902
    751     def items(self, service, nodeIdentifier, maxItems=None, sender=None):
     903    def items(self, service, nodeIdentifier, maxItems=None,
     904              subscriptionIdentifier=None, sender=None):
    752905        """
    753906        Retrieve previously published items from a publish subscribe node.
     
    755908        @param service: The publish subscribe service that keeps the node.
    756909        @type service: L{JID}
     910
    757911        @param nodeIdentifier: The identifier of the node.
    758912        @type nodeIdentifier: C{unicode}
     913
    759914        @param maxItems: Optional limit on the number of retrieved items.
    760915        @type maxItems: C{int}
     916
     917        @param subscriptionIdentifier: Optional subscription identifier. In
     918            case the node has been subscribed to multiple times, this narrows
     919            the results to the specific subscription.
     920        @type subscriptionIdentifier: C{unicode}
    761921        """
    762922        request = PubSubRequest('items')
     
    765925        if maxItems:
    766926            request.maxItems = str(int(maxItems))
     927        request.subscriptionIdentifier = subscriptionIdentifier
    767928        request.sender = sender
    768929
     
    779940
    780941
     942    def getOptions(self, service, nodeIdentifier, subscriber,
     943                         subscriptionIdentifier=None, sender=None):
     944        """
     945        Get subscription options.
     946
     947        @param service: The publish subscribe service that keeps the node.
     948        @type service: L{JID}
     949
     950        @param nodeIdentifier: The identifier of the node.
     951        @type nodeIdentifier: C{unicode}
     952
     953        @param subscriber: The entity subscribed to the node.
     954        @type subscriber: L{JID}
     955
     956        @param subscriptionIdentifier: Optional subscription identifier.
     957        @type subscriptionIdentifier: C{unicode}
     958
     959        @rtype: L{data_form.Form}
     960        """
     961        request = PubSubRequest('optionsGet')
     962        request.recipient = service
     963        request.nodeIdentifier = nodeIdentifier
     964        request.subscriber = subscriber
     965        request.subscriptionIdentifier = subscriptionIdentifier
     966        request.sender = sender
     967
     968        def cb(iq):
     969            form = data_form.findForm(iq.pubsub.options,
     970                                      NS_PUBSUB_SUBSCRIBE_OPTIONS)
     971            form.typeCheck()
     972            return form
     973
     974        d = request.send(self.xmlstream)
     975        d.addCallback(cb)
     976        return d
     977
     978
     979    def setOptions(self, service, nodeIdentifier, subscriber,
     980                         options, subscriptionIdentifier=None, sender=None):
     981        """
     982        Set subscription options.
     983
     984        @param service: The publish subscribe service that keeps the node.
     985        @type service: L{JID}
     986
     987        @param nodeIdentifier: The identifier of the node.
     988        @type nodeIdentifier: C{unicode}
     989
     990        @param subscriber: The entity subscribed to the node.
     991        @type subscriber: L{JID}
     992
     993        @param options: Subscription options.
     994        @type options: C{dict}.
     995
     996        @param subscriptionIdentifier: Optional subscription identifier.
     997        @type subscriptionIdentifier: C{unicode}
     998        """
     999        request = PubSubRequest('optionsSet')
     1000        request.recipient = service
     1001        request.nodeIdentifier = nodeIdentifier
     1002        request.subscriber = subscriber
     1003        request.subscriptionIdentifier = subscriptionIdentifier
     1004        request.sender = sender
     1005
     1006        form = data_form.Form(formType='submit',
     1007                              formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     1008        form.makeFields(options)
     1009        request.options = form
     1010
     1011        d = request.send(self.xmlstream)
     1012        return d
     1013
     1014
    7811015
    7821016class PubSubService(XMPPHandler, IQHandlerMixin):
     
    8081042    """
    8091043
    810     implements(IPubSubService)
     1044    implements(IPubSubService, disco.IDisco)
    8111045
    8121046    iqHandlers = {
     
    8441078        self.resource = resource
    8451079        self.discoIdentity = {'category': 'pubsub',
    846                               'type': 'generic',
     1080                              'type': 'service',
    8471081                              'name': 'Generic Publish-Subscribe Service'}
    8481082
     
    8541088
    8551089
    856     def getDiscoInfo(self, requestor, target, nodeIdentifier):
    857         def toInfo(nodeInfo, info):
     1090    def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
     1091        def toInfo(nodeInfo):
    8581092            if not nodeInfo:
    859                 return info
     1093                return
    8601094
    8611095            (nodeType, metaData) = nodeInfo['type'], nodeInfo['meta-data']
     
    8771111                info.append(form)
    8781112
    879             return info
     1113            return
    8801114
    8811115        info = []
     
    8891123            getInfo = resource.getInfo
    8901124        else:
    891             category, idType, name = self.discoIdentity
     1125            category = self.discoIdentity['category']
     1126            idType = self.discoIdentity['type']
     1127            name = self.discoIdentity['name']
    8921128            identity = disco.DiscoIdentity(category, idType, name)
    8931129            features = self.pubSubFeatures
     
    9001136                         for feature in features])
    9011137
    902         d = getInfo(requestor, target, nodeIdentifier or '')
    903         d.addCallback(toInfo, info)
     1138        d = defer.maybeDeferred(getInfo, requestor, target, nodeIdentifier or '')
     1139        d.addCallback(toInfo)
    9041140        d.addErrback(log.err)
     1141        d.addCallback(lambda _: info)
    9051142        return d
    9061143
    9071144
    908     def getDiscoItems(self, requestor, target, nodeIdentifier):
     1145    def getDiscoItems(self, requestor, target, nodeIdentifier=''):
    9091146        if self.hideNodes:
    9101147            d = defer.succeed([])
     
    9171154        else:
    9181155            d = defer.succeed([])
    919            
    920 
    9211156
    9221157        d.addCallback(lambda nodes: [disco.DiscoItem(target, node)
     
    9481183                handler = getattr(resource, request.verb)
    9491184            except AttributeError:
    950                 # fix lookup feature
    9511185                text = "Request verb: %s" % request.verb
    9521186                return defer.fail(Unsupported('', text))
     
    9541188            d = handler(request)
    9551189        else:
    956             handlerName, argNames = self._legacyHandlers[request.verb]
     1190            try:
     1191                handlerName, argNames = self._legacyHandlers[request.verb]
     1192            except KeyError:
     1193                text = "Request verb: %s" % request.verb
     1194                return defer.fail(Unsupported('', text))
     1195
    9571196            handler = getattr(self, handlerName)
     1197
    9581198            args = [getattr(request, arg) for arg in argNames]
     1199            if 'options' in argNames:
     1200                args[argNames.index('options')] = request.options.getValues()
     1201
    9591202            d = handler(*args)
    9601203
     
    9721215    def _toResponse_subscribe(self, result, resource, request):
    9731216        response = domish.Element((NS_PUBSUB, "pubsub"))
    974         subscription = response.addElement("subscription")
    975         if result.nodeIdentifier:
    976             subscription["node"] = result.nodeIdentifier
    977         subscription["jid"] = result.subscriber.full()
    978         subscription["subscription"] = result.state
     1217        response.addChild(result.toElement(NS_PUBSUB))
    9791218        return response
    9801219
     
    9841223        subscriptions = response.addElement('subscriptions')
    9851224        for subscription in result:
    986             item = subscriptions.addElement('subscription')
    987             item['node'] = subscription.nodeIdentifier
    988             item['jid'] = subscription.subscriber.full()
    989             item['subscription'] = subscription.state
     1225            subscriptions.addChild(subscription.toElement(NS_PUBSUB))
    9901226        return response
    9911227
     
    10131249
    10141250
    1015     def _makeFields(self, options, values):
    1016         fields = []
    1017         for name, value in values.iteritems():
    1018             if name not in options:
    1019                 continue
    1020 
    1021             option = {'var': name}
    1022             option.update(options[name])
    1023             if isinstance(value, list):
    1024                 option['values'] = value
    1025             else:
    1026                 option['value'] = value
    1027             fields.append(data_form.Field.fromDict(option))
    1028         return fields
    1029 
    1030 
    10311251    def _formFromConfiguration(self, resource, values):
    1032         options = resource.getConfigurationOptions()
    1033         fields = self._makeFields(options, values)
     1252        fieldDefs = resource.getConfigurationOptions()
    10341253        form = data_form.Form(formType="form",
    1035                               formNamespace=NS_PUBSUB_NODE_CONFIG,
    1036                               fields=fields)
    1037 
     1254                              formNamespace=NS_PUBSUB_NODE_CONFIG)
     1255        form.makeFields(values, fieldDefs)
    10381256        return form
    10391257
    10401258
    1041     def _checkConfiguration(self, resource, values):
    1042         options = resource.getConfigurationOptions()
    1043         processedValues = {}
    1044 
    1045         for key, value in values.iteritems():
    1046             if key not in options:
    1047                 continue
    1048 
    1049             option = {'var': key}
    1050             option.update(options[key])
    1051             field = data_form.Field.fromDict(option)
    1052             if isinstance(value, list):
    1053                 field.values = value
    1054             else:
    1055                 field.value = value
    1056             field.typeCheck()
    1057 
    1058             if isinstance(value, list):
    1059                 processedValues[key] = field.values
    1060             else:
    1061                 processedValues[key] = field.value
    1062 
    1063         return processedValues
     1259    def _checkConfiguration(self, resource, form):
     1260        fieldDefs = resource.getConfigurationOptions()
     1261        form.typeCheck(fieldDefs, filterUnknown=True)
     1262
     1263
     1264    def _preProcess_create(self, resource, request):
     1265        if request.options:
     1266            self._checkConfiguration(resource, request.options)
     1267        return request
    10641268
    10651269
     
    10921296
    10931297    def _preProcess_configureSet(self, resource, request):
    1094         if request.options:
    1095             request.options = self._checkConfiguration(resource,
    1096                                                        request.options)
     1298        if request.options.formType == 'cancel':
     1299            return None
     1300        else:
     1301            self._checkConfiguration(resource, request.options)
    10971302            return request
    1098         else:
    1099             return None
    11001303
    11011304
     
    11061309
    11071310        for item in result:
     1311            item.uri = NS_PUBSUB
    11081312            items.addChild(item)
    11091313
     
    11321336
    11331337        return message
     1338
     1339
     1340    def _toResponse_affiliationsGet(self, result, resource, request):
     1341        response = domish.Element((NS_PUBSUB_OWNER, 'pubsub'))
     1342        affiliations = response.addElement('affiliations')
     1343
     1344        if request.nodeIdentifier:
     1345            affiliations['node'] = request.nodeIdentifier
     1346
     1347        for entity, affiliation in result.iteritems():
     1348            item = affiliations.addElement('affiliation')
     1349            item['jid'] = entity.full()
     1350            item['affiliation'] = affiliation
     1351
     1352        return response
     1353
    11341354
    11351355    # public methods
     
    11401360                                               nodeIdentifier, subscriber,
    11411361                                               subscriptions)
    1142             message.event.items.children = items
     1362            for item in items:
     1363                item.uri = NS_PUBSUB_EVENT
     1364                message.event.items.addChild(item)
    11431365            self.send(message)
    11441366
  • wokkel/server.py

    • Property exe set to *
    r55 r96  
    11# -*- test-case-name: wokkel.test.test_server -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
  • wokkel/shim.py

    • Property exe set to *
    r27 r96  
    11# -*- test-case-name: wokkel.test.test_shim -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
  • wokkel/subprotocols.py

    • Property exe set to *
    r53 r101  
    11# -*- test-case-name: wokkel.test.test_subprotocols -*-
    22#
    3 # Copyright (c) 2001-2009 Twisted Matrix Laboratories.
     3# Copyright (c) Twisted Matrix Laboratories.
    44# See LICENSE for details.
    55
     
    1111
    1212from twisted.internet import defer
    13 from twisted.python import log
     13from twisted.internet.error import ConnectionDone
     14from twisted.python import failure, log
    1415from twisted.words.protocols.jabber import error, xmlstream
    1516from twisted.words.protocols.jabber.xmlstream import toResponse
     
    9293        """
    9394        self.parent.send(obj)
     95
     96
     97    def request(self, request):
     98        """
     99        Send an IQ request and track the response.
     100
     101        This passes the request to the parent for sending and response
     102        tracking.
     103
     104        @see: L{StreamManager.request}.
     105        """
     106        return self.parent.request(request)
    94107
    95108
     
    156169    @ivar _packetQueue: internal buffer of unsent data. See L{send} for details.
    157170    @type _packetQueue: L{list}
    158     """
     171    @ivar timeout: Default IQ request timeout in seconds.
     172    @type timeout: C{int}
     173    @ivar _reactor: A provider of L{IReactorTime} to track timeouts.
     174    """
     175    timeout = None
     176    _reactor = None
    159177
    160178    logTraffic = False
    161179
    162     def __init__(self, factory):
     180    def __init__(self, factory, reactor=None):
     181        """
     182        Construct a stream manager.
     183
     184        @param factory: The stream factory to connect with.
     185        @param reactor: A provider of L{IReactorTime} to track timeouts.
     186            If not provided, the global reactor will be used.
     187        """
    163188        XMPPHandlerCollection.__init__(self)
    164189        self.xmlstream = None
     
    173198        self.factory = factory
    174199
     200        if reactor is None:
     201            from twisted.internet import reactor
     202        self._reactor = reactor
     203
     204        # Set up IQ response tracking
     205        self._iqDeferreds = {}
     206
    175207
    176208    def addHandler(self, handler):
     
    185217        # get protocol handler up to speed when a connection has already
    186218        # been established
    187         if self.xmlstream and self._initialized:
     219        if self.xmlstream:
    188220            handler.makeConnection(self.xmlstream)
     221        if self._initialized:
    189222            handler.connectionInitialized()
    190223
     
    210243        self.xmlstream = xs
    211244
    212         for e in self:
     245        for e in list(self):
    213246            e.makeConnection(xs)
    214247
     
    221254        C{connectionInitialized} method.
    222255        """
     256
     257        xs.addObserver('/iq[@type="result"]', self._onIQResponse)
     258        xs.addObserver('/iq[@type="error"]', self._onIQResponse)
     259
    223260        # Flush all pending packets
    224261        for p in self._packetQueue:
     
    229266        # Notify all child services which implement
    230267        # the IService interface
    231         for e in self:
     268        for e in list(self):
    232269            e.connectionInitialized()
    233270
     
    247284
    248285
    249     def _disconnected(self, _):
     286    def _disconnected(self, reason):
    250287        """
    251288        Called when the stream has been closed.
     
    258295        self._initialized = False
    259296
     297        # Twisted versions before 11.0 passed an XmlStream here.
     298        if not hasattr(reason, 'trap'):
     299            reason = failure.Failure(ConnectionDone())
     300
    260301        # Notify all child services which implement
    261302        # the IService interface
    262         for e in self:
    263             e.connectionLost(None)
     303        for e in list(self):
     304            e.connectionLost(reason)
     305
     306        # This errbacks all deferreds of iq's for which no response has
     307        # been received with a L{ConnectionLost} failure. Otherwise, the
     308        # deferreds will never be fired.
     309        iqDeferreds = self._iqDeferreds
     310        self._iqDeferreds = {}
     311        for d in iqDeferreds.itervalues():
     312            d.errback(reason)
     313
     314
     315    def _onIQResponse(self, iq):
     316        """
     317        Handle iq response by firing associated deferred.
     318        """
     319        try:
     320            d = self._iqDeferreds[iq["id"]]
     321        except KeyError:
     322            return
     323
     324        del self._iqDeferreds[iq["id"]]
     325        iq.handled = True
     326        if iq['type'] == 'error':
     327            d.errback(error.exceptionFromStanza(iq))
     328        else:
     329            d.callback(iq)
    264330
    265331
     
    278344        else:
    279345            self._packetQueue.append(obj)
     346
     347
     348    def request(self, request):
     349        """
     350        Send an IQ request and track the response.
     351
     352        A request is an IQ L{generic.Stanza} of type C{'get'} or C{'set'}. It
     353        will have its C{toElement} called to render to a L{domish.Element}
     354        which is then sent out over the current stream. If there is no such
     355        stream (yet), it is queued and sent whenever a connection is
     356        established and initialized, just like L{send}.
     357
     358        If the request doesn't have an identifier, it will be assigned a fresh
     359        one, so the response can be tracked.
     360
     361        The deferred that is returned will fire with the L{domish.Element}
     362        representation of the response if it is a result iq. If the response
     363        is an error iq, a corresponding L{error.StanzaError} will be errbacked.
     364
     365        If the connection is closed before a response was received, the deferred
     366        will be errbacked with the reason failure.
     367
     368        A request may also have a timeout, either by setting a default timeout
     369        in L{StreamManager.timeout} or on the C{timeout} attribute of the
     370        request.
     371
     372        @param request: The IQ request.
     373        @type request: L{generic.Request}
     374        """
     375        if (request.stanzaKind != 'iq' or
     376            request.stanzaType not in ('get', 'set')):
     377            return defer.fail(ValueError("Not a request"))
     378
     379        element = request.toElement()
     380
     381        # Make sure we have a trackable id on the stanza
     382        if not request.stanzaID:
     383            element.addUniqueId()
     384            request.stanzaID = element['id']
     385
     386        # Set up iq response tracking
     387        d = defer.Deferred()
     388        self._iqDeferreds[element['id']] = d
     389
     390        timeout = getattr(request, 'timeout', self.timeout)
     391
     392        if timeout is not None:
     393            def onTimeout():
     394                del self._iqDeferreds[element['id']]
     395                d.errback(xmlstream.TimeoutError("IQ timed out"))
     396
     397            call = self._reactor.callLater(timeout, onTimeout)
     398
     399            def cancelTimeout(result):
     400                if call.active():
     401                    call.cancel()
     402
     403                return result
     404
     405            d.addBoth(cancelTimeout)
     406        self.send(element)
     407        return d
    280408
    281409
  • wokkel/test/__init__.py

    • Property exe set to *
    r1 r96  
    1 # Copyright (c) 2003-2007 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
  • wokkel/test/helpers.py

    • Property exe set to *
    r46 r96  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
  • wokkel/test/test_client.py

    • Property exe set to *
    r73 r96  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
  • wokkel/test/test_compat.py

    • Property exe set to *
    r63 r96  
    11# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
    2 # Copyright (c) 2008-2009 Ralph Meijer
     2# Copyright (c) Ralph Meijer.
    33# See LICENSE for details.
    44
  • wokkel/test/test_component.py

    • Property exe set to *
    r54 r96  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    369369        xs = self.xmlstream
    370370        xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
    371         xs.sid = '1234'
     371        xs.sid = u'1234'
    372372        theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
    373373        xs.authenticator.onHandshake(theHash)
     
    390390        xs.sendStreamError = streamErrors.append
    391391
    392         xs.sid = '1234'
     392        xs.sid = u'1234'
    393393        theHash = '1234'
    394394        xs.authenticator.onHandshake(theHash)
  • wokkel/test/test_data_form.py

    • Property exe set to *
    r56 r96  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    2020
    2121    def test_toElement(self):
     22        """
     23        An option is an option element with a value child with the option value.
     24        """
     25        option = data_form.Option('value')
     26        element = option.toElement()
     27
     28        self.assertEqual('option', element.name)
     29        self.assertEqual(NS_X_DATA, element.uri)
     30        self.assertEqual(NS_X_DATA, element.value.uri)
     31        self.assertEqual('value', unicode(element.value))
     32        self.assertFalse(element.hasAttribute('label'))
     33
     34
     35    def test_toElementLabel(self):
     36        """
     37        A label is rendered as an attribute on the option element.
     38        """
    2239        option = data_form.Option('value', 'label')
    2340        element = option.toElement()
    24         self.assertEquals('option', element.name)
    25         self.assertEquals(NS_X_DATA, element.uri)
    26         self.assertEquals('label', element['label'])
    27         self.assertEquals('value', element.value.name)
    28         self.assertEquals(NS_X_DATA, element.value.uri)
    29         self.assertEquals('value', unicode(element.value))
     41
     42        self.assertEqual('option', element.name)
     43        self.assertEqual(NS_X_DATA, element.uri)
     44        self.assertEqual(NS_X_DATA, element.value.uri)
     45        self.assertEqual('value', unicode(element.value))
     46        self.assertEqual('label', element['label'])
     47
     48
     49    def test_fromElement(self):
     50        """
     51        An option has a child element with the option value.
     52        """
     53        element = domish.Element((NS_X_DATA, 'option'))
     54        element.addElement('value', content='value')
     55        option = data_form.Option.fromElement(element)
     56
     57        self.assertEqual('value', option.value)
     58        self.assertIdentical(None, option.label)
     59
     60
     61    def test_fromElementLabel(self):
     62        """
     63        An option label is an attribute on the option element.
     64        """
     65
     66        element = domish.Element((NS_X_DATA, 'option'))
     67        element.addElement('value', content='value')
     68        element['label'] = 'label'
     69        option = data_form.Option.fromElement(element)
     70
     71        self.assertEqual('label', option.label)
     72
     73
     74    def test_fromElementNoValue(self):
     75        """
     76        An option MUST have a value.
     77        """
     78        element = domish.Element((NS_X_DATA, 'option'))
     79        self.assertRaises(data_form.Error,
     80                          data_form.Option.fromElement, element)
     81
     82
     83    def test_repr(self):
     84        """
     85        The representation of an Option is equal to how it is created.
     86        """
     87        option = data_form.Option('value', 'label')
     88        self.assertEqual("""Option('value', 'label')""", repr(option))
    3089
    3190
     
    43102        self.assertEqual('text-single', field.fieldType)
    44103        self.assertEqual('test', field.var)
     104
     105
     106    def test_labelAndOptions(self):
     107        """
     108        The label should be set, even if there are options with labels as dict.
     109        """
     110        field = data_form.Field(label='test',
     111                                options={'test2': 'test 2', 'test3': 'test 3'})
     112        self.assertEqual('test', field.label)
     113
     114
     115    def test_repr(self):
     116        """
     117        The repr of a field should be equal to its initialization.
     118        """
     119        field = data_form.Field('list-single', var='test', label='label',
     120                               desc='desc', required=True, value='test',
     121                               options=[data_form.Option('test')])
     122        self.assertEqual("""Field(fieldType='list-single', """
     123                         """var='test', label='label', """
     124                         """desc='desc', required=True, """
     125                         """values=['test'], """
     126                         """options=[Option('test')])""",
     127                         repr(field))
    45128
    46129
     
    61144
    62145
    63     def test_toElementTypeNotListSingle(self):
    64         """
    65         Always render the field type, if different from list-single.
     146    def test_toElementTypeNotTextSingle(self):
     147        """
     148        Always render the field type, if different from text-single.
    66149        """
    67150        field = data_form.Field('hidden', var='test')
     
    69152
    70153        self.assertEquals('hidden', element.getAttribute('type'))
     154
     155
     156    def test_toElementSingleValue(self):
     157        """
     158        A single value should yield only one value element.
     159        """
     160        field = data_form.Field('list-multi', var='test', value='test')
     161        element = field.toElement()
     162
     163        children = list(element.elements())
     164        self.assertEqual(1, len(children))
     165
     166
     167    def test_toElementMultipleValues(self):
     168        """
     169        A field with no type and multiple values should render all values.
     170        """
     171        field = data_form.Field('list-multi', var='test',
     172                                values=['test', 'test2'])
     173        element = field.toElement()
     174
     175        children = list(element.elements())
     176        self.assertEqual(2, len(children))
    71177
    72178
     
    131237
    132238    def test_toElementJID(self):
     239        """
     240        A JID value should render to text.
     241        """
    133242        field = data_form.Field(fieldType='jid-single', var='test',
    134243                                value=jid.JID(u'test@example.org'))
    135244        element = field.toElement()
    136245        self.assertEqual(u'test@example.org', unicode(element.value))
     246
     247
     248    def test_toElementJIDTextSingle(self):
     249        """
     250        A JID value should render to text if field type is text-single.
     251        """
     252        field = data_form.Field(fieldType='text-single', var='test',
     253                                value=jid.JID(u'test@example.org'))
     254        element = field.toElement()
     255        self.assertEqual(u'test@example.org', unicode(element.value))
     256
     257
     258    def test_toElementBoolean(self):
     259        """
     260        A boolean value should render to text.
     261        """
     262        field = data_form.Field(fieldType='boolean', var='test',
     263                                value=True)
     264        element = field.toElement()
     265        self.assertEqual(u'true', unicode(element.value))
     266
     267
     268    def test_toElementBooleanTextSingle(self):
     269        """
     270        A boolean value should render to text if the field type is text-single.
     271        """
     272        field = data_form.Field(var='test', value=True)
     273        element = field.toElement()
     274        self.assertEqual(u'true', unicode(element.value))
     275
     276
     277    def test_toElementNoType(self):
     278        """
     279        A field with no type should not have a type attribute.
     280        """
     281        field = data_form.Field(None, var='test', value='test')
     282        element = field.toElement()
     283        self.assertFalse(element.hasAttribute('type'))
     284
     285
     286    def test_toElementNoTypeMultipleValues(self):
     287        """
     288        A field with no type and multiple values should render all values.
     289        """
     290        field = data_form.Field(None, var='test', values=['test', 'test2'])
     291        element = field.toElement()
     292
     293        self.assertFalse(element.hasAttribute('type'))
     294        children = list(element.elements())
     295        self.assertEqual(2, len(children))
    137296
    138297
     
    251410        self.assertEquals(u'user@example.org', field.value)
    252411
     412
    253413    def test_fromElementValueJIDMalformed(self):
    254414        """
     
    276436
    277437
     438    def test_fromElementDesc(self):
     439        """
     440        Field descriptions are in a desc child element.
     441        """
     442        element = domish.Element((NS_X_DATA, 'field'))
     443        element.addElement('desc', content=u'My description')
     444        field = data_form.Field.fromElement(element)
     445        self.assertEqual(u'My description', field.desc)
     446
     447
     448    def test_fromElementOption(self):
     449        """
     450        Field descriptions are in a desc child element.
     451        """
     452        element = domish.Element((NS_X_DATA, 'field'))
     453        element.addElement('option').addElement('value', content=u'option1')
     454        element.addElement('option').addElement('value', content=u'option2')
     455        field = data_form.Field.fromElement(element)
     456        self.assertEqual(2, len(field.options))
     457
     458
     459    def test_fromElementRequired(self):
     460        """
     461        Required fields have a required child element.
     462        """
     463        element = domish.Element((NS_X_DATA, 'field'))
     464        element.addElement('required')
     465        field = data_form.Field.fromElement(element)
     466        self.assertTrue(field.required)
     467
     468
     469    def test_fromElementChildOtherNamespace(self):
     470        """
     471        Child elements from another namespace are ignored.
     472        """
     473        element = domish.Element((NS_X_DATA, 'field'))
     474        element['var'] = 'test'
     475        child = element.addElement(('myns', 'value'))
     476        field = data_form.Field.fromElement(element)
     477
     478        self.assertIdentical(None, field.value)
     479
     480
     481    def test_fromDict(self):
     482        """
     483        A named field with a value can be created by providing a dictionary.
     484        """
     485        fieldDict = {'var': 'test', 'value': 'text'}
     486        field = data_form.Field.fromDict(fieldDict)
     487        self.assertEqual('test', field.var)
     488        self.assertEqual('text', field.value)
     489
     490
     491    def test_fromDictFieldType(self):
     492        """
     493        The field type is set using the key 'type'.
     494        """
     495        fieldDict = {'type': 'boolean'}
     496        field = data_form.Field.fromDict(fieldDict)
     497        self.assertEqual('boolean', field.fieldType)
     498
     499
     500    def test_fromDictOptions(self):
     501        """
     502        The field options are set using the key 'options'.
     503
     504        The options are represented as a dictionary keyed by option,
     505        with the optional label as value.
     506        """
     507        fieldDict = {'options': {'value1': 'label1',
     508                                 'value2': 'label2'}}
     509        field = data_form.Field.fromDict(fieldDict)
     510        self.assertEqual(2, len(field.options))
     511        options = {}
     512        for option in field.options:
     513            options[option.value] = option.label
     514
     515        self.assertEqual(options, fieldDict['options'])
     516
    278517
    279518class FormTest(unittest.TestCase):
     
    289528        form = data_form.Form('result')
    290529        self.assertEqual('result', form.formType)
     530
    291531
    292532    def test_toElement(self):
     
    304544
    305545
     546    def test_toElementTitle(self):
     547        """
     548        A title is rendered as a child element with the title as CDATA.
     549        """
     550        form = data_form.Form('form', title='Bot configuration')
     551        element = form.toElement()
     552
     553        elements = list(element.elements())
     554        self.assertEqual(1, len(elements))
     555        title = elements[0]
     556        self.assertEqual('title', title.name)
     557        self.assertEqual(NS_X_DATA, title.uri)
     558        self.assertEqual('Bot configuration', unicode(title))
     559
     560
     561    def test_toElementInstructions(self):
     562        """
     563        Instructions are rendered as child elements with CDATA.
     564        """
     565        form = data_form.Form('form', instructions=['Fill out this form!'])
     566        element = form.toElement()
     567
     568        elements = list(element.elements())
     569        self.assertEqual(1, len(elements))
     570        instructions = elements[0]
     571        self.assertEqual('instructions', instructions.name)
     572        self.assertEqual(NS_X_DATA, instructions.uri)
     573        self.assertEqual('Fill out this form!', unicode(instructions))
     574
     575
     576    def test_toElementInstructionsMultiple(self):
     577        """
     578        Instructions render as one element per instruction, in order.
     579        """
     580        form = data_form.Form('form', instructions=['Fill out this form!',
     581                                                    'no really'])
     582        element = form.toElement()
     583
     584        elements = list(element.elements())
     585        self.assertEqual(2, len(elements))
     586        instructions1 = elements[0]
     587        instructions2 = elements[1]
     588        self.assertEqual('instructions', instructions1.name)
     589        self.assertEqual(NS_X_DATA, instructions1.uri)
     590        self.assertEqual('Fill out this form!', unicode(instructions1))
     591        self.assertEqual('instructions', instructions2.name)
     592        self.assertEqual(NS_X_DATA, instructions2.uri)
     593        self.assertEqual('no really', unicode(instructions2))
     594
     595
     596    def test_toElementFormType(self):
     597        """
     598        The form type is rendered as a hidden field with name FORM_TYPE.
     599        """
     600        form = data_form.Form('form', formNamespace='jabber:bot')
     601        element = form.toElement()
     602
     603        elements = list(element.elements())
     604        self.assertEqual(1, len(elements))
     605        formTypeField = elements[0]
     606        self.assertEqual('field', formTypeField.name)
     607        self.assertEqual(NS_X_DATA, formTypeField.uri)
     608        self.assertEqual('FORM_TYPE', formTypeField['var'])
     609        self.assertEqual('hidden', formTypeField['type'])
     610        self.assertEqual('jabber:bot', unicode(formTypeField.value))
     611
     612
     613    def test_toElementFields(self):
     614        """
     615        Fields are rendered as child elements, in order.
     616        """
     617        fields = [data_form.Field('fixed', value='Section 1'),
     618                  data_form.Field('text-single',
     619                                  var='botname',
     620                                  label='The name of your bot'),
     621                  data_form.Field('text-multi',
     622                                  var='description',
     623                                  label='Helpful description of your bot'),
     624                  data_form.Field('boolean',
     625                                  var='public',
     626                                  label='Public bot?',
     627                                  required=True)
     628                 ]
     629        form = data_form.Form('form', fields=fields)
     630        element = form.toElement()
     631
     632        elements = list(element.elements())
     633        self.assertEqual(4, len(elements))
     634        for field in elements:
     635            self.assertEqual('field', field.name)
     636            self.assertEqual(NS_X_DATA, field.uri)
     637
     638        # Check order
     639        self.assertEqual('fixed', elements[0]['type'])
     640        self.assertEqual('botname', elements[1]['var'])
     641        self.assertEqual('description', elements[2]['var'])
     642        self.assertEqual('public', elements[3]['var'])
     643
     644
    306645    def test_fromElement(self):
    307646        """
     
    348687
    349688        self.assertEquals(['instruction'], form.instructions)
     689
    350690
    351691    def test_fromElementInstructions2(self):
     
    378718        self.assertIn('field2', form.fields)
    379719        self.assertEquals('field2', form.fieldList[1].var)
     720
     721
     722    def test_fromElementFormType(self):
     723        """
     724        The form type is a hidden field named FORM_TYPE.
     725        """
     726        element = domish.Element((NS_X_DATA, 'x'))
     727        field = element.addElement('field')
     728        field['var'] = 'FORM_TYPE'
     729        field['type'] = 'hidden'
     730        field.addElement('value', content='myns')
     731        form = data_form.Form.fromElement(element)
     732
     733        self.assertNotIn('FORM_TYPE', form.fields)
     734        self.assertEqual('myns', form.formNamespace)
     735
     736    def test_fromElementFormTypeNotHidden(self):
     737        """
     738        A non-hidden field named FORM_TYPE does not set the form type.
     739        """
     740        element = domish.Element((NS_X_DATA, 'x'))
     741        field = element.addElement('field')
     742        field['var'] = 'FORM_TYPE'
     743        field.addElement('value', content='myns')
     744        form = data_form.Form.fromElement(element)
     745
     746        self.assertIn('FORM_TYPE', form.fields)
     747        self.assertIdentical(None, form.formNamespace)
     748
     749
     750    def test_fromElementChildOtherNamespace(self):
     751        """
     752        Child elements from another namespace are ignored.
     753        """
     754        element = domish.Element((NS_X_DATA, 'x'))
     755        element['type'] = 'result'
     756        field = element.addElement(('myns', 'field'))
     757        field['var'] = 'test'
     758        form = data_form.Form.fromElement(element)
     759
     760        self.assertEqual(0, len(form.fields))
     761
     762
     763    def test_repr(self):
     764        """
     765        The repr of a form should be equal to its initialization.
     766        """
     767        form = data_form.Form('form', title='title', instructions=['instr'],
     768                                      formNamespace='myns',
     769                                      fields=[data_form.Field('fixed',
     770                                                              value='test')])
     771        self.assertEqual("""Form(formType='form', title='title', """
     772                         """instructions=['instr'], formNamespace='myns', """
     773                         """fields=[Field(fieldType='fixed', """
     774                         """values=['test'])])""",
     775                         repr(form))
     776
     777
     778    def test_addField(self):
     779        """
     780        A field should occur in fieldList.
     781        """
     782        form = data_form.Form('result')
     783        field = data_form.Field('fixed', value='Section 1')
     784        form.addField(field)
     785        self.assertEqual([field], form.fieldList)
     786
     787
     788    def test_addFieldTwice(self):
     789        """
     790        Fields occur in fieldList in the order they were added.
     791        """
     792        form = data_form.Form('result')
     793        field1 = data_form.Field('fixed', value='Section 1')
     794        field2 = data_form.Field('fixed', value='Section 2')
     795        form.addField(field1)
     796        form.addField(field2)
     797        self.assertEqual([field1, field2], form.fieldList)
     798
     799
     800    def test_addFieldNotNamed(self):
     801        """
     802        A non-named field should not occur in fields.
     803        """
     804        form = data_form.Form('result')
     805        field = data_form.Field('fixed', value='Section 1')
     806        form.addField(field)
     807        self.assertEqual({}, form.fields)
     808
     809
     810    def test_addFieldNamed(self):
     811        """
     812        A named field should occur in fields.
     813        """
     814        form = data_form.Form('result')
     815        field = data_form.Field(var='test')
     816        form.addField(field)
     817        self.assertEqual({'test': field}, form.fields)
     818
     819
     820    def test_addFieldTwiceNamed(self):
     821        """
     822        A second named field should occur in fields.
     823        """
     824        form = data_form.Form('result')
     825        field1 = data_form.Field(var='test')
     826        field2 = data_form.Field(var='test2')
     827        form.addField(field2)
     828        form.addField(field1)
     829        self.assertEqual({'test': field1, 'test2': field2}, form.fields)
     830
     831
     832    def test_addFieldSameName(self):
     833        """
     834        A named field cannot occur twice.
     835        """
     836        form = data_form.Form('result')
     837        field1 = data_form.Field(var='test', value='value')
     838        field2 = data_form.Field(var='test', value='value2')
     839        form.addField(field1)
     840        self.assertRaises(data_form.Error, form.addField, field2)
     841
     842
     843    def test_removeField(self):
     844        """
     845        A removed field should not occur in fieldList.
     846        """
     847        form = data_form.Form('result')
     848        field = data_form.Field('fixed', value='Section 1')
     849        form.addField(field)
     850        form.removeField(field)
     851        self.assertNotIn(field, form.fieldList)
     852
     853
     854    def test_removeFieldNamed(self):
     855        """
     856        A removed named field should not occur in fields.
     857        """
     858        form = data_form.Form('result')
     859        field = data_form.Field(var='test', value='test1')
     860        form.addField(field)
     861        form.removeField(field)
     862        self.assertNotIn('test', form.fields)
     863
     864
     865    def test_makeField(self):
     866        """
     867        Fields can be created from a dict of values and a dict of field defs.
     868        """
     869        fieldDefs = {
     870                "pubsub#persist_items":
     871                    {"type": "boolean",
     872                     "label": "Persist items to storage"},
     873                "pubsub#deliver_payloads":
     874                    {"type": "boolean",
     875                     "label": "Deliver payloads with event notifications"},
     876                "pubsub#creator":
     877                    {"type": "jid-single",
     878                     "label": "The JID of the node creator"},
     879                "pubsub#description":
     880                    {"type": "text-single",
     881                     "label": "A description of the node"},
     882                "pubsub#owner":
     883                    {"type": "jid-single",
     884                     "label": "Owner of the node"},
     885                }
     886        values = {'pubsub#deliver_payloads': '0',
     887                  'pubsub#persist_items': True,
     888                  'pubsub#description': 'a great node',
     889                  'pubsub#owner': jid.JID('user@example.org'),
     890                  'x-myfield': ['a', 'b']}
     891
     892        form = data_form.Form('submit')
     893        form.makeFields(values, fieldDefs)
     894
     895        # Check that the expected fields have been created
     896        self.assertIn('pubsub#deliver_payloads', form.fields)
     897        self.assertIn('pubsub#persist_items', form.fields)
     898        self.assertIn('pubsub#description', form.fields)
     899        self.assertIn('pubsub#owner', form.fields)
     900
     901        # This field is not created because there is no value for it.
     902        self.assertNotIn('pubsub#creator', form.fields)
     903
     904        # This field is not created because it does not appear in fieldDefs
     905        # and filterUnknown defaults to True
     906        self.assertNotIn('x-myfield', form.fields)
     907
     908        # Check properties the created fields
     909        self.assertEqual('boolean',
     910                         form.fields['pubsub#deliver_payloads'].fieldType)
     911        self.assertEqual('0',
     912                         form.fields['pubsub#deliver_payloads'].value)
     913        self.assertEqual('Deliver payloads with event notifications',
     914                         form.fields['pubsub#deliver_payloads'].label)
     915        self.assertEqual(True,
     916                         form.fields['pubsub#persist_items'].value)
     917
     918
     919    def test_makeFieldNotFilterUnknown(self):
     920        """
     921        Fields can be created from a dict of values and a dict of field defs.
     922        """
     923        fieldDefs = {
     924                "pubsub#persist_items":
     925                    {"type": "boolean",
     926                     "label": "Persist items to storage"},
     927                }
     928        values = {'x-myfield': ['a', 'b']}
     929
     930        form = data_form.Form('submit')
     931        form.makeFields(values, fieldDefs, filterUnknown=False)
     932
     933        field = form.fields['x-myfield']
     934        self.assertEqual(None, field.fieldType)
     935        self.assertEqual(values, form.getValues())
     936
     937
     938    def test_makeFieldsUnknownTypeJID(self):
     939        """
     940        Without type, a single JID value sets field type jid-single.
     941        """
     942        values = {'pubsub#creator': jid.JID('user@example.org')}
     943        form = data_form.Form('result')
     944        form.makeFields(values)
     945
     946        field = form.fields['pubsub#creator']
     947        self.assertEqual(None, field.fieldType)
     948        self.assertEqual(values, form.getValues())
     949
     950
     951    def test_makeFieldsUnknownTypeJIDMulti(self):
     952        """
     953        Without type, multiple JID values sets field type jid-multi.
     954        """
     955        values = {'pubsub#contact': [jid.JID('user@example.org'),
     956                                     jid.JID('other@example.org')]}
     957        form = data_form.Form('result')
     958        form.makeFields(values)
     959
     960        field = form.fields['pubsub#contact']
     961        self.assertEqual(None, field.fieldType)
     962        self.assertEqual(values, form.getValues())
     963
     964
     965    def test_makeFieldsUnknownTypeBoolean(self):
     966        """
     967        Without type, a boolean value sets field type boolean.
     968        """
     969        values = {'pubsub#persist_items': True}
     970        form = data_form.Form('result')
     971        form.makeFields(values)
     972
     973        field = form.fields['pubsub#persist_items']
     974        self.assertEqual(None, field.fieldType)
     975        self.assertEqual(values, form.getValues())
     976
     977
     978    def test_makeFieldsUnknownTypeListMulti(self):
     979        """
     980        Without type, multiple values sets field type list-multi.
     981        """
     982        values = {'pubsub#show-values': ['chat', 'online', 'away']}
     983        form = data_form.Form('result')
     984        form.makeFields(values)
     985
     986        field = form.fields['pubsub#show-values']
     987        self.assertEqual(None, field.fieldType)
     988        self.assertEqual(values, form.getValues())
     989
     990
     991    def test_getValues(self):
     992        """
     993        Each named field is represented in the values, keyed by name.
     994        """
     995        fields = [data_form.Field(var='botname', value='The Jabber Bot'),
     996                  data_form.Field('boolean', var='public', value=True),
     997                  data_form.Field('list-multi', var='features',
     998                                                values=['news', 'search'])]
     999        form = data_form.Form('submit', fields=fields)
     1000        values = form.getValues()
     1001        self.assertEqual({'botname': 'The Jabber Bot',
     1002                          'public': True,
     1003                          'features': ['news', 'search']},
     1004                         values)
     1005
     1006
     1007    def test_getValuesOneValueTypeMulti(self):
     1008        """
     1009        A single value for a multi-value field type is returned in a list.
     1010        """
     1011        fields = [data_form.Field('list-multi', var='features',
     1012                                                values=['news'])]
     1013        form = data_form.Form('submit', fields=fields)
     1014        values = form.getValues()
     1015        self.assertEqual({'features': ['news']}, values)
     1016
     1017
     1018    def test_getValuesMultipleValuesNoType(self):
     1019        """
     1020        Multiple values for a field without type are returned in a list.
     1021        """
     1022        fields = [data_form.Field(None, var='features',
     1023                                        values=['news', 'search'])]
     1024        form = data_form.Form('submit', fields=fields)
     1025        values = form.getValues()
     1026        self.assertEqual({'features': ['news', 'search']}, values)
     1027
     1028
     1029    def test_getValuesMultipleValuesTypeSingle(self):
     1030        """
     1031        Multiple values for a single-value field type returns the first value.
     1032        """
     1033        fields = [data_form.Field('text-single', var='features',
     1034                                        values=['news', 'search'])]
     1035        form = data_form.Form('submit', fields=fields)
     1036        values = form.getValues()
     1037        self.assertEqual({'features': 'news'}, values)
     1038
     1039
     1040    def test_typeCheckKnownFieldChecked(self):
     1041        """
     1042        Known fields are type checked.
     1043        """
     1044        checked = []
     1045        fieldDefs = {"pubsub#description":
     1046                        {"type": "text-single",
     1047                         "label": "A description of the node"}}
     1048        form = data_form.Form('submit')
     1049        form.addField(data_form.Field(var='pubsub#description',
     1050                                      value='a node'))
     1051        field = form.fields['pubsub#description']
     1052        field.typeCheck = lambda : checked.append(None)
     1053        form.typeCheck(fieldDefs)
     1054
     1055        self.assertEqual([None], checked)
     1056
     1057
     1058    def test_typeCheckKnownFieldNoType(self):
     1059        """
     1060        Known fields without a type get the type of the field definition.
     1061        """
     1062        checked = []
     1063        fieldDefs = {"pubsub#description":
     1064                        {"type": "text-single",
     1065                         "label": "A description of the node"}}
     1066        form = data_form.Form('submit')
     1067        form.addField(data_form.Field(None, var='pubsub#description',
     1068                                            value='a node'))
     1069        field = form.fields['pubsub#description']
     1070        field.typeCheck = lambda : checked.append(None)
     1071        form.typeCheck(fieldDefs)
     1072
     1073        self.assertEqual('text-single', field.fieldType)
     1074        self.assertEqual([None], checked)
     1075
     1076
     1077    def test_typeCheckWrongFieldType(self):
     1078        """
     1079        A field should have the same type as the field definition.
     1080        """
     1081        checked = []
     1082        fieldDefs = {"pubsub#description":
     1083                        {"type": "text-single",
     1084                         "label": "A description of the node"}}
     1085        form = data_form.Form('submit')
     1086        form.addField(data_form.Field('list-single', var='pubsub#description',
     1087                                                     value='a node'))
     1088        field = form.fields['pubsub#description']
     1089        field.typeCheck = lambda : checked.append(None)
     1090
     1091        self.assertRaises(TypeError, form.typeCheck, fieldDefs)
     1092        self.assertEqual([], checked)
     1093
     1094
     1095    def test_typeCheckDefaultTextSingle(self):
     1096        """
     1097        If a field definition has no type, use text-single.
     1098        """
     1099        checked = []
     1100        fieldDefs = {"pubsub#description":
     1101                        {"label": "A description of the node"}}
     1102        form = data_form.Form('submit')
     1103        form.addField(data_form.Field('text-single', var='pubsub#description',
     1104                                                     value='a node'))
     1105        field = form.fields['pubsub#description']
     1106        field.typeCheck = lambda : checked.append(None)
     1107        form.typeCheck(fieldDefs)
     1108
     1109        self.assertEqual([None], checked)
     1110
     1111
     1112    def test_typeCheckUnknown(self):
     1113        """
     1114        Unknown fields are checked, not removed if filterUnknown False.
     1115        """
     1116        checked = []
     1117        fieldDefs = {}
     1118        form = data_form.Form('submit')
     1119        form.addField(data_form.Field('list-single', var='pubsub#description',
     1120                                                     value='a node'))
     1121        field = form.fields['pubsub#description']
     1122        field.typeCheck = lambda : checked.append(None)
     1123        form.typeCheck(fieldDefs, filterUnknown=False)
     1124
     1125        self.assertIn('pubsub#description', form.fields)
     1126        self.assertEqual([None], checked)
     1127
     1128
     1129    def test_typeCheckUnknownNoType(self):
     1130        """
     1131        Unknown fields without type are not checked.
     1132        """
     1133        checked = []
     1134        fieldDefs = {}
     1135        form = data_form.Form('submit')
     1136        form.addField(data_form.Field(None, var='pubsub#description',
     1137                                            value='a node'))
     1138        field = form.fields['pubsub#description']
     1139        field.typeCheck = lambda : checked.append(None)
     1140        form.typeCheck(fieldDefs, filterUnknown=False)
     1141
     1142        self.assertIn('pubsub#description', form.fields)
     1143        self.assertEqual([], checked)
     1144
     1145
     1146    def test_typeCheckUnknownRemoved(self):
     1147        """
     1148        Unknown fields are not checked, and removed if filterUnknown True.
     1149        """
     1150        checked = []
     1151        fieldDefs = {}
     1152        form = data_form.Form('submit')
     1153        form.addField(data_form.Field('list-single', var='pubsub#description',
     1154                                                     value='a node'))
     1155        field = form.fields['pubsub#description']
     1156        field.typeCheck = lambda : checked.append(None)
     1157        form.typeCheck(fieldDefs, filterUnknown=True)
     1158
     1159        self.assertNotIn('pubsub#description', form.fields)
     1160        self.assertEqual([], checked)
     1161
     1162
     1163
     1164class FindFormTest(unittest.TestCase):
     1165    """
     1166    Tests for L{data_form.findForm}.
     1167    """
     1168
     1169    def test_findForm(self):
     1170        element = domish.Element((None, 'test'))
     1171        theForm = data_form.Form('submit', formNamespace='myns')
     1172        element.addChild(theForm.toElement())
     1173        form = data_form.findForm(element, 'myns')
     1174        self.assertEqual('myns', form.formNamespace)
     1175
     1176
     1177    def test_noFormType(self):
     1178        element = domish.Element((None, 'test'))
     1179        otherForm = data_form.Form('submit')
     1180        element.addChild(otherForm.toElement())
     1181        form = data_form.findForm(element, 'myns')
     1182        self.assertIdentical(None, form)
     1183
     1184
     1185    def test_noFormTypeCancel(self):
     1186        """
     1187        Cancelled forms don't have a FORM_TYPE field, the first is returned.
     1188        """
     1189        element = domish.Element((None, 'test'))
     1190        cancelledForm = data_form.Form('cancel')
     1191        element.addChild(cancelledForm.toElement())
     1192        form = data_form.findForm(element, 'myns')
     1193        self.assertEqual('cancel', form.formType)
     1194
     1195
     1196    def test_otherFormType(self):
     1197        """
     1198        Forms with other FORM_TYPEs are ignored.
     1199        """
     1200        element = domish.Element((None, 'test'))
     1201        otherForm = data_form.Form('submit', formNamespace='otherns')
     1202        element.addChild(otherForm.toElement())
     1203        form = data_form.findForm(element, 'myns')
     1204        self.assertIdentical(None, form)
     1205
     1206
     1207    def test_otherFormTypeCancel(self):
     1208        """
     1209        Cancelled forms with another FORM_TYPE are ignored.
     1210        """
     1211        element = domish.Element((None, 'test'))
     1212        cancelledForm = data_form.Form('cancel', formNamespace='otherns')
     1213        element.addChild(cancelledForm.toElement())
     1214        form = data_form.findForm(element, 'myns')
     1215        self.assertIdentical(None, form)
     1216
     1217
     1218    def test_noElement(self):
     1219        """
     1220        When None is passed as element, None is returned.
     1221        """
     1222        element = None
     1223        form = data_form.findForm(element, 'myns')
     1224        self.assertIdentical(None, form)
     1225
     1226
     1227    def test_noForm(self):
     1228        """
     1229        When no child element is a form, None is returned.
     1230        """
     1231        element = domish.Element((None, 'test'))
     1232        form = data_form.findForm(element, 'myns')
     1233        self.assertIdentical(None, form)
     1234    def test_typeCheckNoFieldDefs(self):
     1235        """
     1236        If there are no field defs, an empty dictionary is assumed.
     1237        """
     1238        checked = []
     1239        form = data_form.Form('submit')
     1240        form.addField(data_form.Field('list-single', var='pubsub#description',
     1241                                                     value='a node'))
     1242        field = form.fields['pubsub#description']
     1243        field.typeCheck = lambda : checked.append(None)
     1244        form.typeCheck()
     1245
     1246        self.assertIn('pubsub#description', form.fields)
     1247        self.assertEqual([None], checked)
  • wokkel/test/test_disco.py

    • Property exe set to *
    r67 r103  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    1010from twisted.internet import defer
    1111from twisted.trial import unittest
     12from twisted.words.protocols.jabber.error import StanzaError
    1213from twisted.words.protocols.jabber.jid import JID
    1314from twisted.words.protocols.jabber.xmlstream import toResponse
    14 from twisted.words.xish import domish
     15from twisted.words.xish import domish, utility
    1516
    1617from wokkel import data_form, disco
     
    477478        """
    478479        self.stub = XmlStreamStub()
     480        self.patch(XMPPHandler, 'request', self.request)
    479481        self.protocol = disco.DiscoClientProtocol()
    480         self.protocol.xmlstream = self.stub.xmlstream
    481         self.protocol.connectionInitialized()
     482
     483
     484    def request(self, request):
     485        element = request.toElement()
     486        self.stub.xmlstream.send(element)
     487        return defer.Deferred()
    482488
    483489
     
    511517        element[u'jid'] = u"test2.example.org"
    512518
    513         self.stub.send(response)
     519        d.callback(response)
    514520        return d
    515521
     
    527533        response = toResponse(iq, u'result')
    528534        response.addElement((NS_DISCO_ITEMS, u'query'))
    529         self.stub.send(response)
    530 
     535
     536        d.callback(response)
    531537        return d
    532538
     
    566572        element[u'var'] = u'http://jabber.org/protocol/muc'
    567573
    568         self.stub.send(response)
     574        d.callback(response)
    569575        return d
    570576
     
    575581        """
    576582        d = self.protocol.requestInfo(JID(u'example.org'),
    577                                        sender=JID(u'test.example.org'))
     583                                      sender=JID(u'test.example.org'))
    578584
    579585        iq = self.stub.output[-1]
     
    582588        response = toResponse(iq, u'result')
    583589        response.addElement((NS_DISCO_INFO, u'query'))
    584         self.stub.send(response)
    585 
     590
     591        d.callback(response)
    586592        return d
    587593
     
    595601    def setUp(self):
    596602        self.service = disco.DiscoHandler()
     603
     604
     605    def test_connectionInitializedObserveInfo(self):
     606        """
     607        An observer for Disco Info requests is setup on stream initialization.
     608        """
     609        xml = """<iq from='test@example.com' to='example.com'
     610                     type='get'>
     611                   <query xmlns='%s'/>
     612                 </iq>""" % NS_DISCO_INFO
     613
     614        def handleRequest(iq):
     615            called.append(iq)
     616
     617        called = []
     618        self.service.xmlstream = utility.EventDispatcher()
     619        self.service.handleRequest = handleRequest
     620        self.service.connectionInitialized()
     621        self.service.xmlstream.dispatch(parseXml(xml))
     622        self.assertEqual(1, len(called))
     623
     624
     625    def test_connectionInitializedObserveItems(self):
     626        """
     627        An observer for Disco Items requests is setup on stream initialization.
     628        """
     629        xml = """<iq from='test@example.com' to='example.com'
     630                     type='get'>
     631                   <query xmlns='%s'/>
     632                 </iq>""" % NS_DISCO_ITEMS
     633
     634        def handleRequest(iq):
     635            called.append(iq)
     636
     637        called = []
     638        self.service.xmlstream = utility.EventDispatcher()
     639        self.service.handleRequest = handleRequest
     640        self.service.connectionInitialized()
     641        self.service.xmlstream.dispatch(parseXml(xml))
     642        self.assertEqual(1, len(called))
    597643
    598644
     
    636682
    637683
     684    def test_onDiscoInfoWithNoFromAttribute(self):
     685        """
     686        Disco info request without a from attribute has requestor None.
     687        """
     688        xml = """<iq to='example.com'
     689                     type='get'>
     690                   <query xmlns='%s'/>
     691                 </iq>""" % NS_DISCO_INFO
     692
     693        def info(requestor, target, nodeIdentifier):
     694            self.assertEqual(None, requestor)
     695
     696            return defer.succeed([
     697                disco.DiscoIdentity('dummy', 'generic', 'Generic Dummy Entity'),
     698                disco.DiscoFeature('jabber:iq:version')
     699            ])
     700
     701        self.service.info = info
     702        d = self.handleRequest(xml)
     703        return d
     704
     705
     706    def test_onDiscoInfoWithNoToAttribute(self):
     707        """
     708        Disco info request without a to attribute has target None.
     709        """
     710        xml = """<iq from='test@example.com'
     711                     type='get'>
     712                   <query xmlns='%s'/>
     713                 </iq>""" % NS_DISCO_INFO
     714
     715        def info(requestor, target, nodeIdentifier):
     716            self.assertEqual(JID('test@example.com'), requestor)
     717
     718            return defer.succeed([
     719                disco.DiscoIdentity('dummy', 'generic', 'Generic Dummy Entity'),
     720                disco.DiscoFeature('jabber:iq:version')
     721            ])
     722
     723        self.service.info = info
     724        d = self.handleRequest(xml)
     725        return d
     726
     727
    638728    def test_onDiscoInfoWithNode(self):
    639729        """
     
    658748        self.service.info = info
    659749        d = self.handleRequest(xml)
     750        d.addCallback(cb)
     751        return d
     752
     753
     754    def test_onDiscoInfoWithNodeNoResults(self):
     755        """
     756        An info request for a node with no results returns items-not-found.
     757        """
     758        xml = """<iq from='test@example.com' to='example.com'
     759                     type='get'>
     760                   <query xmlns='%s' node='test'/>
     761                 </iq>""" % NS_DISCO_INFO
     762
     763        def cb(exc):
     764            self.assertEquals('item-not-found', exc.condition)
     765
     766        def info(requestor, target, nodeIdentifier):
     767            self.assertEqual('test', nodeIdentifier)
     768
     769            return defer.succeed([])
     770
     771        self.service.info = info
     772        d = self.handleRequest(xml)
     773        self.assertFailure(d, StanzaError)
    660774        d.addCallback(cb)
    661775        return d
  • wokkel/test/test_generic.py

    • Property exe set to *
    r35 r102  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    88from twisted.trial import unittest
    99from twisted.words.xish import domish
     10from twisted.words.protocols.jabber.jid import JID
    1011
    1112from wokkel import generic
     
    8788        self.pipe.sink.send(element)
    8889        self.assertEquals([element], called)
     90
     91
     92
     93class RequestTest(unittest.TestCase):
     94    """
     95    Tests for L{generic.Request}.
     96    """
     97
     98    def setUp(self):
     99        self.request = generic.Request()
     100
     101
     102    def test_toElementStanzaKind(self):
     103        """
     104        A request is an iq stanza.
     105        """
     106        element = self.request.toElement()
     107        self.assertIdentical(None, element.uri)
     108        self.assertEquals('iq', element.name)
     109
     110
     111    def test_toElementStanzaType(self):
     112        """
     113        The request has type 'get'.
     114        """
     115        self.assertEquals('get', self.request.stanzaType)
     116        element = self.request.toElement()
     117        self.assertEquals('get', element.getAttribute('type'))
     118
     119
     120    def test_toElementStanzaTypeSet(self):
     121        """
     122        The request has type 'set'.
     123        """
     124        self.request.stanzaType = 'set'
     125        element = self.request.toElement()
     126        self.assertEquals('set', element.getAttribute('type'))
     127
     128
     129    def test_toElementStanzaID(self):
     130        """
     131        A request, when rendered, has an identifier.
     132        """
     133        element = self.request.toElement()
     134        self.assertNotIdentical(None, self.request.stanzaID)
     135        self.assertEquals(self.request.stanzaID, element.getAttribute('id'))
     136
     137
     138    def test_toElementRecipient(self):
     139        """
     140        A request without recipient, has no 'to' attribute.
     141        """
     142        self.request = generic.Request(recipient=JID('other@example.org'))
     143        self.assertEquals(JID('other@example.org'), self.request.recipient)
     144        element = self.request.toElement()
     145        self.assertEquals(u'other@example.org', element.getAttribute('to'))
     146
     147
     148    def test_toElementRecipientNone(self):
     149        """
     150        A request without recipient, has no 'to' attribute.
     151        """
     152        element = self.request.toElement()
     153        self.assertFalse(element.hasAttribute('to'))
     154
     155
     156    def test_toElementSender(self):
     157        """
     158        A request with sender, has a 'from' attribute.
     159        """
     160        self.request = generic.Request(sender=JID('user@example.org'))
     161        self.assertEquals(JID('user@example.org'), self.request.sender)
     162        element = self.request.toElement()
     163        self.assertEquals(u'user@example.org', element.getAttribute('from'))
     164
     165
     166    def test_toElementSenderNone(self):
     167        """
     168        A request without sender, has no 'from' attribute.
     169        """
     170        element = self.request.toElement()
     171        self.assertFalse(element.hasAttribute('from'))
     172
     173
     174    def test_timeoutDefault(self):
     175        """
     176        The default is no timeout.
     177        """
     178        self.assertIdentical(None, self.request.timeout)
  • wokkel/test/test_ping.py

    • Property exe set to *
    r65 r96  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    134134
    135135
     136    def test_onPingHandled(self):
     137        """
     138        The ping handler should mark the stanza as handled.
     139        """
     140        xml = """<iq from='test@example.com' to='example.com' type='get'>
     141                   <ping xmlns='urn:xmpp:ping'/>
     142                 </iq>"""
     143        iq = parseXml(xml)
     144        self.stub.send(iq)
     145
     146        self.assertTrue(iq.handled)
     147
     148
    136149    def test_interfaceIDisco(self):
    137150        """
  • wokkel/test/test_pubsub.py

    • Property exe set to *
    r59 r97  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    2020
    2121NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
    22 NS_PUBSUB_CONFIG = 'http://jabber.org/protocol/pubsub#node_config'
     22NS_PUBSUB_NODE_CONFIG = 'http://jabber.org/protocol/pubsub#node_config'
    2323NS_PUBSUB_ERRORS = 'http://jabber.org/protocol/pubsub#errors'
    2424NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event'
    2525NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner'
    2626NS_PUBSUB_META_DATA = 'http://jabber.org/protocol/pubsub#meta-data'
     27NS_PUBSUB_SUBSCRIBE_OPTIONS = 'http://jabber.org/protocol/pubsub#subscribe_options'
    2728
    2829def calledAsync(fn):
     
    4142
    4243    return d, func
     44
     45
     46class SubscriptionTest(unittest.TestCase):
     47    """
     48    Tests for L{pubsub.Subscription}.
     49    """
     50
     51    def test_fromElement(self):
     52        """
     53        fromElement parses a subscription from XML DOM.
     54        """
     55        xml = """
     56        <subscription node='test' jid='user@example.org/Home'
     57                      subscription='pending'/>
     58        """
     59        subscription = pubsub.Subscription.fromElement(parseXml(xml))
     60        self.assertEqual('test', subscription.nodeIdentifier)
     61        self.assertEqual(JID('user@example.org/Home'), subscription.subscriber)
     62        self.assertEqual('pending', subscription.state)
     63        self.assertIdentical(None, subscription.subscriptionIdentifier)
     64
     65
     66    def test_fromElementWithSubscriptionIdentifier(self):
     67        """
     68        A subscription identifier in the subscription should be parsed, too.
     69        """
     70        xml = """
     71        <subscription node='test' jid='user@example.org/Home' subid='1234'
     72                      subscription='pending'/>
     73        """
     74        subscription = pubsub.Subscription.fromElement(parseXml(xml))
     75        self.assertEqual('1234', subscription.subscriptionIdentifier)
     76
     77
     78    def test_toElement(self):
     79        """
     80        Rendering a Subscription should yield the proper attributes.
     81        """
     82        subscription = pubsub.Subscription('test',
     83                                           JID('user@example.org/Home'),
     84                                           'pending')
     85        element = subscription.toElement()
     86        self.assertEqual('subscription', element.name)
     87        self.assertEqual(None, element.uri)
     88        self.assertEqual('test', element.getAttribute('node'))
     89        self.assertEqual('user@example.org/Home', element.getAttribute('jid'))
     90        self.assertEqual('pending', element.getAttribute('subscription'))
     91        self.assertFalse(element.hasAttribute('subid'))
     92
     93
     94    def test_toElementEmptyNodeIdentifier(self):
     95        """
     96        The empty node identifier should not yield a node attribute.
     97        """
     98        subscription = pubsub.Subscription('',
     99                                           JID('user@example.org/Home'),
     100                                           'pending')
     101        element = subscription.toElement()
     102        self.assertFalse(element.hasAttribute('node'))
     103
     104
     105    def test_toElementWithSubscriptionIdentifier(self):
     106        """
     107        The subscription identifier, if set, is in the subid attribute.
     108        """
     109        subscription = pubsub.Subscription('test',
     110                                           JID('user@example.org/Home'),
     111                                           'pending',
     112                                           subscriptionIdentifier='1234')
     113        element = subscription.toElement()
     114        self.assertEqual('1234', element.getAttribute('subid'))
     115
    43116
    44117
     
    113186
    114187
     188    def test_eventItemsError(self):
     189        """
     190        An error message with embedded event should not be handled.
     191
     192        This test uses an items event, which should not result in itemsReceived
     193        being called. In general message.handled should be False.
     194        """
     195        message = domish.Element((None, 'message'))
     196        message['from'] = 'pubsub.example.org'
     197        message['to'] = 'user@example.org/home'
     198        message['type'] = 'error'
     199        event = message.addElement((NS_PUBSUB_EVENT, 'event'))
     200        items = event.addElement('items')
     201        items['node'] = 'test'
     202
     203        class UnexpectedCall(Exception):
     204            pass
     205
     206        def itemsReceived(event):
     207            raise UnexpectedCall("Unexpected call to itemsReceived")
     208
     209        self.protocol.itemsReceived = itemsReceived
     210        self.stub.send(message)
     211        self.assertFalse(message.handled)
     212
     213
    115214    def test_eventDelete(self):
    116215        """
     
    266365        iq = self.stub.output[-1]
    267366        self.assertEquals('user@example.org', iq['from'])
     367
     368        response = toResponse(iq, 'result')
     369        self.stub.send(response)
     370        return d
     371
     372
     373    def test_createNodeWithConfig(self):
     374        """
     375        Test sending create request with configuration options
     376        """
     377
     378        options = {
     379            'pubsub#title': 'Princely Musings (Atom)',
     380            'pubsub#deliver_payloads': True,
     381            'pubsub#persist_items': '1',
     382            'pubsub#max_items': '10',
     383            'pubsub#access_model': 'open',
     384            'pubsub#type': 'http://www.w3.org/2005/Atom',
     385        }
     386
     387        d = self.protocol.createNode(JID('pubsub.example.org'), 'test',
     388                                     sender=JID('user@example.org'),
     389                                     options=options)
     390
     391        iq = self.stub.output[-1]
     392
     393        # check if there is exactly one configure element
     394        children = list(domish.generateElementsQNamed(iq.pubsub.children,
     395                                                      'configure', NS_PUBSUB))
     396        self.assertEqual(1, len(children))
     397
     398        # check that it has a configuration form
     399        form = data_form.findForm(children[0], NS_PUBSUB_NODE_CONFIG)
     400        self.assertEqual('submit', form.formType)
     401
    268402
    269403        response = toResponse(iq, 'result')
     
    408542
    409543
     544    def test_subscribeReturnsSubscription(self):
     545        """
     546        A successful subscription should return a Subscription instance.
     547        """
     548        def cb(subscription):
     549            self.assertEqual(JID('user@example.org'), subscription.subscriber)
     550
     551        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
     552                                      JID('user@example.org'))
     553        d.addCallback(cb)
     554
     555        iq = self.stub.output[-1]
     556
     557        response = toResponse(iq, 'result')
     558        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     559        subscription = pubsub.addElement('subscription')
     560        subscription['node'] = 'test'
     561        subscription['jid'] = 'user@example.org'
     562        subscription['subscription'] = 'subscribed'
     563        self.stub.send(response)
     564        return d
     565
     566
    410567    def test_subscribePending(self):
    411568        """
     
    448605
    449606
    450     def test_subscribeWithSender(self):
    451         """
    452         Test sending subscription request from a specific JID.
    453         """
     607    def test_subscribeWithOptions(self):
     608        options = {'pubsub#deliver': False}
     609
    454610        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
    455                                       JID('user@example.org'),
    456                                       sender=JID('user@example.org'))
    457 
     611                                    JID('user@example.org'),
     612                                    options=options)
    458613        iq = self.stub.output[-1]
    459         self.assertEquals('user@example.org', iq['from'])
    460 
     614
     615        # Check options present
     616        childNames = []
     617        for element in iq.pubsub.elements():
     618            if element.uri == NS_PUBSUB:
     619                childNames.append(element.name)
     620
     621        self.assertEqual(['subscribe', 'options'], childNames)
     622        form = data_form.findForm(iq.pubsub.options,
     623                                  NS_PUBSUB_SUBSCRIBE_OPTIONS)
     624        self.assertEqual('submit', form.formType)
     625        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     626        self.assertEqual(options, form.getValues())
     627
     628        # Send response
    461629        response = toResponse(iq, 'result')
    462630        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     
    465633        subscription['jid'] = 'user@example.org'
    466634        subscription['subscription'] = 'subscribed'
     635        self.stub.send(response)
     636
     637        return d
     638
     639
     640    def test_subscribeWithSender(self):
     641        """
     642        Test sending subscription request from a specific JID.
     643        """
     644        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
     645                                      JID('user@example.org'),
     646                                      sender=JID('user@example.org'))
     647
     648        iq = self.stub.output[-1]
     649        self.assertEquals('user@example.org', iq['from'])
     650
     651        response = toResponse(iq, 'result')
     652        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     653        subscription = pubsub.addElement('subscription')
     654        subscription['node'] = 'test'
     655        subscription['jid'] = 'user@example.org'
     656        subscription['subscription'] = 'subscribed'
     657        self.stub.send(response)
     658        return d
     659
     660
     661    def test_subscribeReturningSubscriptionIdentifier(self):
     662        """
     663        Test sending subscription request with subscription identifier.
     664        """
     665        def cb(subscription):
     666            self.assertEqual('1234', subscription.subscriptionIdentifier)
     667
     668        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
     669                                      JID('user@example.org'))
     670        d.addCallback(cb)
     671
     672        iq = self.stub.output[-1]
     673
     674        response = toResponse(iq, 'result')
     675        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     676        subscription = pubsub.addElement('subscription')
     677        subscription['node'] = 'test'
     678        subscription['jid'] = 'user@example.org'
     679        subscription['subscription'] = 'subscribed'
     680        subscription['subid'] = '1234'
    467681        self.stub.send(response)
    468682        return d
     
    506720
    507721
     722    def test_unsubscribeWithSubscriptionIdentifier(self):
     723        """
     724        Test sending unsubscription request with subscription identifier.
     725        """
     726        d = self.protocol.unsubscribe(JID('pubsub.example.org'), 'test',
     727                                      JID('user@example.org'),
     728                                      subscriptionIdentifier='1234')
     729
     730        iq = self.stub.output[-1]
     731        child = iq.pubsub.unsubscribe
     732        self.assertEquals('1234', child['subid'])
     733
     734        self.stub.send(toResponse(iq, 'result'))
     735        return d
     736
     737
    508738    def test_items(self):
    509739        """
     
    572802
    573803
    574     def test_itemsWithSender(self):
    575         """
    576         Test sending items request from a specific JID.
     804    def test_itemsWithSubscriptionIdentifier(self):
     805        """
     806        Test sending items request with a subscription identifier.
    577807        """
    578808
    579809        d = self.protocol.items(JID('pubsub.example.org'), 'test',
    580                                sender=JID('user@example.org'))
     810                               subscriptionIdentifier='1234')
    581811
    582812        iq = self.stub.output[-1]
    583         self.assertEquals('user@example.org', iq['from'])
     813        child = iq.pubsub.items
     814        self.assertEquals('1234', child['subid'])
    584815
    585816        response = toResponse(iq, 'result')
     
    591822
    592823
     824    def test_itemsWithSender(self):
     825        """
     826        Test sending items request from a specific JID.
     827        """
     828
     829        d = self.protocol.items(JID('pubsub.example.org'), 'test',
     830                               sender=JID('user@example.org'))
     831
     832        iq = self.stub.output[-1]
     833        self.assertEquals('user@example.org', iq['from'])
     834
     835        response = toResponse(iq, 'result')
     836        items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items')
     837        items['node'] = 'test'
     838
     839        self.stub.send(response)
     840        return d
     841
     842
     843    def test_getOptions(self):
     844        def cb(form):
     845            self.assertEqual('form', form.formType)
     846            self.assertEqual(NS_PUBSUB_SUBSCRIBE_OPTIONS, form.formNamespace)
     847            field = form.fields['pubsub#deliver']
     848            self.assertEqual('boolean', field.fieldType)
     849            self.assertIdentical(True, field.value)
     850            self.assertEqual('Enable delivery?', field.label)
     851
     852        d = self.protocol.getOptions(JID('pubsub.example.org'), 'test',
     853                                     JID('user@example.org'),
     854                                     sender=JID('user@example.org'))
     855        d.addCallback(cb)
     856
     857        iq = self.stub.output[-1]
     858        self.assertEqual('pubsub.example.org', iq.getAttribute('to'))
     859        self.assertEqual('get', iq.getAttribute('type'))
     860        self.assertEqual('pubsub', iq.pubsub.name)
     861        self.assertEqual(NS_PUBSUB, iq.pubsub.uri)
     862        children = list(domish.generateElementsQNamed(iq.pubsub.children,
     863                                                      'options', NS_PUBSUB))
     864        self.assertEqual(1, len(children))
     865        child = children[0]
     866        self.assertEqual('test', child['node'])
     867
     868        self.assertEqual(0, len(child.children))
     869
     870        # Send response
     871        form = data_form.Form('form', formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     872        form.addField(data_form.Field('boolean', var='pubsub#deliver',
     873                                                 label='Enable delivery?',
     874                                                 value=True))
     875        response = toResponse(iq, 'result')
     876        response.addElement((NS_PUBSUB, 'pubsub'))
     877        response.pubsub.addElement('options')
     878        response.pubsub.options.addChild(form.toElement())
     879        self.stub.send(response)
     880
     881        return d
     882
     883
     884    def test_getOptionsWithSubscriptionIdentifier(self):
     885        """
     886        Getting options with a subid should have the subid in the request.
     887        """
     888
     889        d = self.protocol.getOptions(JID('pubsub.example.org'), 'test',
     890                                     JID('user@example.org'),
     891                                     sender=JID('user@example.org'),
     892                                     subscriptionIdentifier='1234')
     893
     894        iq = self.stub.output[-1]
     895        child = iq.pubsub.options
     896        self.assertEqual('1234', child['subid'])
     897
     898        # Send response
     899        form = data_form.Form('form', formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     900        form.addField(data_form.Field('boolean', var='pubsub#deliver',
     901                                                 label='Enable delivery?',
     902                                                 value=True))
     903        response = toResponse(iq, 'result')
     904        response.addElement((NS_PUBSUB, 'pubsub'))
     905        response.pubsub.addElement('options')
     906        response.pubsub.options.addChild(form.toElement())
     907        self.stub.send(response)
     908
     909        return d
     910
     911
     912    def test_setOptions(self):
     913        """
     914        setOptions should send out a options-set request.
     915        """
     916        options = {'pubsub#deliver': False}
     917
     918        d = self.protocol.setOptions(JID('pubsub.example.org'), 'test',
     919                                     JID('user@example.org'),
     920                                     options,
     921                                     sender=JID('user@example.org'))
     922
     923        iq = self.stub.output[-1]
     924        self.assertEqual('pubsub.example.org', iq.getAttribute('to'))
     925        self.assertEqual('set', iq.getAttribute('type'))
     926        self.assertEqual('pubsub', iq.pubsub.name)
     927        self.assertEqual(NS_PUBSUB, iq.pubsub.uri)
     928        children = list(domish.generateElementsQNamed(iq.pubsub.children,
     929                                                      'options', NS_PUBSUB))
     930        self.assertEqual(1, len(children))
     931        child = children[0]
     932        self.assertEqual('test', child['node'])
     933
     934        form = data_form.findForm(child, NS_PUBSUB_SUBSCRIBE_OPTIONS)
     935        self.assertEqual('submit', form.formType)
     936        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     937        self.assertEqual(options, form.getValues())
     938
     939        response = toResponse(iq, 'result')
     940        self.stub.send(response)
     941
     942        return d
     943
     944
     945    def test_setOptionsWithSubscriptionIdentifier(self):
     946        """
     947        setOptions should send out a options-set request with subid.
     948        """
     949        options = {'pubsub#deliver': False}
     950
     951        d = self.protocol.setOptions(JID('pubsub.example.org'), 'test',
     952                                     JID('user@example.org'),
     953                                     options,
     954                                     subscriptionIdentifier='1234',
     955                                     sender=JID('user@example.org'))
     956
     957        iq = self.stub.output[-1]
     958        child = iq.pubsub.options
     959        self.assertEqual('1234', child['subid'])
     960
     961        form = data_form.findForm(child, NS_PUBSUB_SUBSCRIBE_OPTIONS)
     962        self.assertEqual('submit', form.formType)
     963        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     964        self.assertEqual(options, form.getValues())
     965
     966        response = toResponse(iq, 'result')
     967        self.stub.send(response)
     968
     969        return d
     970
    593971
    594972class PubSubRequestTest(unittest.TestCase):
     973
     974    def test_fromElementUnknown(self):
     975        """
     976        An unknown verb raises NotImplementedError.
     977        """
     978
     979        xml = """
     980        <iq type='set' to='pubsub.example.org'
     981                       from='user@example.org'>
     982          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     983            <non-existing-verb/>
     984          </pubsub>
     985        </iq>
     986        """
     987
     988        self.assertRaises(NotImplementedError,
     989                          pubsub.PubSubRequest.fromElement, parseXml(xml))
     990
     991
     992    def test_fromElementKnownBadCombination(self):
     993        """
     994        Multiple verbs in an unknown configuration raises NotImplementedError.
     995        """
     996
     997        xml = """
     998        <iq type='set' to='pubsub.example.org'
     999                       from='user@example.org'>
     1000          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1001             <publish/>
     1002             <create/>
     1003          </pubsub>
     1004        </iq>
     1005        """
     1006
     1007        self.assertRaises(NotImplementedError,
     1008                          pubsub.PubSubRequest.fromElement, parseXml(xml))
    5951009
    5961010    def test_fromElementPublish(self):
     
    6391053
    6401054
     1055    def test_fromElementPublishItemsOptions(self):
     1056        """
     1057        Test parsing a publish request with items and options.
     1058
     1059        Note that publishing options are not supported, but passing them
     1060        shouldn't affect processing of the publish request itself.
     1061        """
     1062
     1063        xml = """
     1064        <iq type='set' to='pubsub.example.org'
     1065                       from='user@example.org'>
     1066          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1067            <publish node='test'>
     1068              <item id="item1"/>
     1069              <item id="item2"/>
     1070            </publish>
     1071            <publish-options/>
     1072          </pubsub>
     1073        </iq>
     1074        """
     1075
     1076        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1077        self.assertEqual(2, len(request.items))
     1078        self.assertEqual(u'item1', request.items[0]["id"])
     1079        self.assertEqual(u'item2', request.items[1]["id"])
     1080
    6411081    def test_fromElementPublishNoNode(self):
    6421082        """
     
    7191159        self.assertEqual('jid-required', err.appCondition.name)
    7201160
     1161
     1162    def test_fromElementSubscribeWithOptions(self):
     1163        """
     1164        Test parsing a subscription request.
     1165        """
     1166
     1167        xml = """
     1168        <iq type='set' to='pubsub.example.org'
     1169                       from='user@example.org'>
     1170          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1171            <subscribe node='test' jid='user@example.org/Home'/>
     1172            <options>
     1173              <x xmlns="jabber:x:data" type='submit'>
     1174                <field var='FORM_TYPE' type='hidden'>
     1175                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     1176                </field>
     1177                <field var='pubsub#deliver' type='boolean'
     1178                       label='Enable delivery?'>
     1179                  <value>1</value>
     1180                </field>
     1181              </x>
     1182            </options>
     1183          </pubsub>
     1184        </iq>
     1185        """
     1186
     1187        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1188        self.assertEqual('subscribe', request.verb)
     1189        request.options.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     1190        self.assertEqual({'pubsub#deliver': True}, request.options.getValues())
     1191
     1192
     1193    def test_fromElementSubscribeWithOptionsBadFormType(self):
     1194        """
     1195        The options form should have the right type.
     1196        """
     1197
     1198        xml = """
     1199        <iq type='set' to='pubsub.example.org'
     1200                       from='user@example.org'>
     1201          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1202            <subscribe node='test' jid='user@example.org/Home'/>
     1203            <options>
     1204              <x xmlns="jabber:x:data" type='result'>
     1205                <field var='FORM_TYPE' type='hidden'>
     1206                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     1207                </field>
     1208                <field var='pubsub#deliver' type='boolean'
     1209                       label='Enable delivery?'>
     1210                  <value>1</value>
     1211                </field>
     1212              </x>
     1213            </options>
     1214          </pubsub>
     1215        </iq>
     1216        """
     1217
     1218        err = self.assertRaises(error.StanzaError,
     1219                                pubsub.PubSubRequest.fromElement,
     1220                                parseXml(xml))
     1221        self.assertEqual('bad-request', err.condition)
     1222        self.assertEqual("Unexpected form type 'result'", err.text)
     1223        self.assertEqual(None, err.appCondition)
     1224
     1225
     1226    def test_fromElementSubscribeWithOptionsEmpty(self):
     1227        """
     1228        When no (suitable) form is found, the options are empty.
     1229        """
     1230
     1231        xml = """
     1232        <iq type='set' to='pubsub.example.org'
     1233                       from='user@example.org'>
     1234          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1235            <subscribe node='test' jid='user@example.org/Home'/>
     1236            <options/>
     1237          </pubsub>
     1238        </iq>
     1239        """
     1240
     1241        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1242        self.assertEqual('subscribe', request.verb)
     1243        self.assertEqual({}, request.options.getValues())
     1244
     1245
    7211246    def test_fromElementUnsubscribe(self):
    7221247        """
     
    7411266
    7421267
     1268    def test_fromElementUnsubscribeWithSubscriptionIdentifier(self):
     1269        """
     1270        Test parsing an unsubscription request with subscription identifier.
     1271        """
     1272
     1273        xml = """
     1274        <iq type='set' to='pubsub.example.org'
     1275                       from='user@example.org'>
     1276          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1277            <unsubscribe node='test' jid='user@example.org/Home'
     1278                         subid='1234'/>
     1279          </pubsub>
     1280        </iq>
     1281        """
     1282
     1283        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1284        self.assertEqual('1234', request.subscriptionIdentifier)
     1285
     1286
    7431287    def test_fromElementUnsubscribeNoJID(self):
    7441288        """
     
    7771321        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
    7781322        self.assertEqual('optionsGet', request.verb)
     1323        self.assertEqual(JID('user@example.org'), request.sender)
     1324        self.assertEqual(JID('pubsub.example.org'), request.recipient)
     1325        self.assertEqual('test', request.nodeIdentifier)
     1326        self.assertEqual(JID('user@example.org/Home'), request.subscriber)
     1327
     1328
     1329    def test_fromElementOptionsGetWithSubscriptionIdentifier(self):
     1330        """
     1331        Test parsing a request for getting subscription options with subid.
     1332        """
     1333
     1334        xml = """
     1335        <iq type='get' to='pubsub.example.org'
     1336                       from='user@example.org'>
     1337          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1338            <options node='test' jid='user@example.org/Home'
     1339                     subid='1234'/>
     1340          </pubsub>
     1341        </iq>
     1342        """
     1343
     1344        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1345        self.assertEqual('1234', request.subscriptionIdentifier)
    7791346
    7801347
     
    8061373        self.assertEqual('test', request.nodeIdentifier)
    8071374        self.assertEqual(JID('user@example.org/Home'), request.subscriber)
    808         self.assertEqual({'pubsub#deliver': '1'}, request.options)
    809 
    810 
    811     def test_fromElementOptionsSetCancel(self):
    812         """
    813         Test parsing a request for cancelling setting subscription options.
    814         """
    815 
    816         xml = """
    817         <iq type='set' to='pubsub.example.org'
    818                        from='user@example.org'>
    819           <pubsub xmlns='http://jabber.org/protocol/pubsub'>
    820             <options node='test' jid='user@example.org/Home'>
    821               <x xmlns='jabber:x:data' type='cancel'/>
    822             </options>
    823           </pubsub>
    824         </iq>
    825         """
    826 
    827         request = pubsub.PubSubRequest.fromElement(parseXml(xml))
    828         self.assertEqual({}, request.options)
    829 
    830 
    831     def test_fromElementOptionsSetBadFormType(self):
    832         """
    833         On a options set request unknown fields should be ignored.
    834         """
    835 
    836         xml = """
    837         <iq type='set' to='pubsub.example.org'
    838                        from='user@example.org'>
    839           <pubsub xmlns='http://jabber.org/protocol/pubsub'>
    840             <options node='test' jid='user@example.org/Home'>
    841               <x xmlns='jabber:x:data' type='result'>
     1375        self.assertEqual({'pubsub#deliver': '1'}, request.options.getValues())
     1376
     1377
     1378    def test_fromElementOptionsSetWithSubscriptionIdentifier(self):
     1379        """
     1380        Test parsing a request for setting subscription options with subid.
     1381        """
     1382
     1383        xml = """
     1384        <iq type='set' to='pubsub.example.org'
     1385                       from='user@example.org'>
     1386          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1387            <options node='test' jid='user@example.org/Home'
     1388                     subid='1234'>
     1389              <x xmlns='jabber:x:data' type='submit'>
    8421390                <field var='FORM_TYPE' type='hidden'>
    843                   <value>http://jabber.org/protocol/pubsub#node_config</value>
     1391                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
    8441392                </field>
    8451393                <field var='pubsub#deliver'><value>1</value></field>
     
    8501398        """
    8511399
     1400        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1401        self.assertEqual('1234', request.subscriptionIdentifier)
     1402
     1403
     1404    def test_fromElementOptionsSetCancel(self):
     1405        """
     1406        Test parsing a request for cancelling setting subscription options.
     1407        """
     1408
     1409        xml = """
     1410        <iq type='set' to='pubsub.example.org'
     1411                       from='user@example.org'>
     1412          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1413            <options node='test' jid='user@example.org/Home'>
     1414              <x xmlns='jabber:x:data' type='cancel'/>
     1415            </options>
     1416          </pubsub>
     1417        </iq>
     1418        """
     1419
     1420        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1421        self.assertEqual('cancel', request.options.formType)
     1422
     1423
     1424    def test_fromElementOptionsSetBadFormType(self):
     1425        """
     1426        On a options set request unknown fields should be ignored.
     1427        """
     1428
     1429        xml = """
     1430        <iq type='set' to='pubsub.example.org'
     1431                       from='user@example.org'>
     1432          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1433            <options node='test' jid='user@example.org/Home'>
     1434              <x xmlns='jabber:x:data' type='result'>
     1435                <field var='FORM_TYPE' type='hidden'>
     1436                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     1437                </field>
     1438                <field var='pubsub#deliver'><value>1</value></field>
     1439              </x>
     1440            </options>
     1441          </pubsub>
     1442        </iq>
     1443        """
     1444
     1445        err = self.assertRaises(error.StanzaError,
     1446                                pubsub.PubSubRequest.fromElement,
     1447                                parseXml(xml))
     1448        self.assertEqual('bad-request', err.condition)
     1449        self.assertEqual("Unexpected form type 'result'", err.text)
     1450        self.assertEqual(None, err.appCondition)
     1451
     1452
     1453    def test_fromElementOptionsSetNoForm(self):
     1454        """
     1455        On a options set request a form is required.
     1456        """
     1457
     1458        xml = """
     1459        <iq type='set' to='pubsub.example.org'
     1460                       from='user@example.org'>
     1461          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1462            <options node='test' jid='user@example.org/Home'/>
     1463          </pubsub>
     1464        </iq>
     1465        """
    8521466        err = self.assertRaises(error.StanzaError,
    8531467                                pubsub.PubSubRequest.fromElement,
     
    8571471
    8581472
    859     def test_fromElementOptionsSetNoForm(self):
    860         """
    861         On a options set request a form is required.
    862         """
    863 
    864         xml = """
    865         <iq type='set' to='pubsub.example.org'
    866                        from='user@example.org'>
    867           <pubsub xmlns='http://jabber.org/protocol/pubsub'>
    868             <options node='test' jid='user@example.org/Home'/>
    869           </pubsub>
    870         </iq>
    871         """
    872         err = self.assertRaises(error.StanzaError,
    873                                 pubsub.PubSubRequest.fromElement,
    874                                 parseXml(xml))
    875         self.assertEqual('bad-request', err.condition)
    876         self.assertEqual(None, err.appCondition)
    877 
    878 
    8791473    def test_fromElementSubscriptions(self):
    8801474        """
     
    9361530        self.assertEqual(JID('pubsub.example.org'), request.recipient)
    9371531        self.assertEqual('mynode', request.nodeIdentifier)
     1532        self.assertIdentical(None, request.options)
    9381533
    9391534
     
    9561551
    9571552
     1553    def test_fromElementCreateConfigureEmpty(self):
     1554        """
     1555        Test parsing a request to create a node with an empty configuration.
     1556        """
     1557
     1558        xml = """
     1559        <iq type='set' to='pubsub.example.org'
     1560                       from='user@example.org'>
     1561          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1562            <create node='mynode'/>
     1563            <configure/>
     1564          </pubsub>
     1565        </iq>
     1566        """
     1567
     1568        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1569        self.assertEqual({}, request.options.getValues())
     1570        self.assertEqual(u'mynode', request.nodeIdentifier)
     1571
     1572
     1573    def test_fromElementCreateConfigureEmptyWrongOrder(self):
     1574        """
     1575        Test parsing a request to create a node and configure, wrong order.
     1576
     1577        The C{configure} element should come after the C{create} request,
     1578        but we should accept both orders.
     1579        """
     1580
     1581        xml = """
     1582        <iq type='set' to='pubsub.example.org'
     1583                       from='user@example.org'>
     1584          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1585            <configure/>
     1586            <create node='mynode'/>
     1587          </pubsub>
     1588        </iq>
     1589        """
     1590
     1591        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1592        self.assertEqual({}, request.options.getValues())
     1593        self.assertEqual(u'mynode', request.nodeIdentifier)
     1594
     1595
     1596    def test_fromElementCreateConfigure(self):
     1597        """
     1598        Test parsing a request to create a node.
     1599        """
     1600
     1601        xml = """
     1602        <iq type='set' to='pubsub.example.org'
     1603                       from='user@example.org'>
     1604          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1605            <create node='mynode'/>
     1606            <configure>
     1607              <x xmlns='jabber:x:data' type='submit'>
     1608                <field var='FORM_TYPE' type='hidden'>
     1609                  <value>http://jabber.org/protocol/pubsub#node_config</value>
     1610                </field>
     1611                <field var='pubsub#access_model'><value>open</value></field>
     1612                <field var='pubsub#persist_items'><value>0</value></field>
     1613              </x>
     1614            </configure>
     1615          </pubsub>
     1616        </iq>
     1617        """
     1618
     1619        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1620        values = request.options.getValues()
     1621        self.assertIn('pubsub#access_model', values)
     1622        self.assertEqual(u'open', values['pubsub#access_model'])
     1623        self.assertIn('pubsub#persist_items', values)
     1624        self.assertEqual(u'0', values['pubsub#persist_items'])
     1625
     1626
     1627    def test_fromElementCreateConfigureBadFormType(self):
     1628        """
     1629        The form of a node creation request should have the right type.
     1630        """
     1631
     1632        xml = """
     1633        <iq type='set' to='pubsub.example.org'
     1634                       from='user@example.org'>
     1635          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1636            <create node='mynode'/>
     1637            <configure>
     1638              <x xmlns='jabber:x:data' type='result'>
     1639                <field var='FORM_TYPE' type='hidden'>
     1640                  <value>http://jabber.org/protocol/pubsub#node_config</value>
     1641                </field>
     1642                <field var='pubsub#access_model'><value>open</value></field>
     1643                <field var='pubsub#persist_items'><value>0</value></field>
     1644              </x>
     1645            </configure>
     1646          </pubsub>
     1647        </iq>
     1648        """
     1649
     1650        err = self.assertRaises(error.StanzaError,
     1651                                pubsub.PubSubRequest.fromElement,
     1652                                parseXml(xml))
     1653        self.assertEqual('bad-request', err.condition)
     1654        self.assertEqual("Unexpected form type 'result'", err.text)
     1655        self.assertEqual(None, err.appCondition)
     1656
     1657
    9581658    def test_fromElementDefault(self):
    9591659        """
    960         Test parsing a request for the default node configuration.
     1660        Parsing default node configuration request sets required attributes.
     1661
     1662        Besides C{verb}, C{sender} and C{recipient}, we expect C{nodeType}
     1663        to be set. If not passed it receives the default C{u'leaf'}.
    9611664        """
    9621665
     
    9711674
    9721675        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
    973         self.assertEqual('default', request.verb)
    974         self.assertEqual(JID('user@example.org'), request.sender)
    975         self.assertEqual(JID('pubsub.example.org'), request.recipient)
    976         self.assertEqual('leaf', request.nodeType)
     1676        self.assertEquals(u'default', request.verb)
     1677        self.assertEquals(JID('user@example.org'), request.sender)
     1678        self.assertEquals(JID('pubsub.example.org'), request.recipient)
     1679        self.assertEquals(u'leaf', request.nodeType)
    9771680
    9781681
    9791682    def test_fromElementDefaultCollection(self):
    9801683        """
    981         Parsing a request for the default configuration extracts the node type.
     1684        Parsing default request for collection sets nodeType to collection.
    9821685        """
    9831686
     
    10021705
    10031706        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
    1004         self.assertEqual('collection', request.nodeType)
     1707        self.assertEquals('collection', request.nodeType)
    10051708
    10061709
     
    10541757        self.assertEqual('test', request.nodeIdentifier)
    10551758        self.assertEqual({'pubsub#deliver_payloads': '0',
    1056                           'pubsub#persist_items': '1'}, request.options)
     1759                          'pubsub#persist_items': '1'},
     1760                         request.options.getValues())
    10571761
    10581762
     
    10741778
    10751779        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
    1076         self.assertEqual({}, request.options)
     1780        self.assertEqual('cancel', request.options.formType)
    10771781
    10781782
    10791783    def test_fromElementConfigureSetBadFormType(self):
    10801784        """
    1081         On a node configuration set request unknown fields should be ignored.
     1785        The form of a node configuraton set request should have the right type.
    10821786        """
    10831787
     
    11031807                                parseXml(xml))
    11041808        self.assertEqual('bad-request', err.condition)
     1809        self.assertEqual("Unexpected form type 'result'", err.text)
    11051810        self.assertEqual(None, err.appCondition)
    11061811
     
    11451850        self.assertEqual('test', request.nodeIdentifier)
    11461851        self.assertIdentical(None, request.maxItems)
     1852        self.assertIdentical(None, request.subscriptionIdentifier)
    11471853        self.assertEqual([], request.itemIdentifiers)
     1854
     1855
     1856    def test_fromElementItemsSubscriptionIdentifier(self):
     1857        """
     1858        Test parsing an items request with subscription identifier.
     1859        """
     1860        xml = """
     1861        <iq type='get' to='pubsub.example.org'
     1862                       from='user@example.org'>
     1863          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     1864            <items node='test' subid='1234'/>
     1865          </pubsub>
     1866        </iq>
     1867        """
     1868
     1869        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     1870        self.assertEqual('1234', request.subscriptionIdentifier)
    11481871
    11491872
     
    12341957
    12351958
     1959    def test_interfaceIDisco(self):
     1960        """
     1961        Do instances of L{pubsub.PubSubService} provide L{iwokkel.IDisco}?
     1962        """
     1963        verify.verifyObject(iwokkel.IDisco, self.service)
     1964
     1965
    12361966    def test_connectionMade(self):
    12371967        """
     
    13362066        d = self.service.getDiscoInfo(JID('user@example.org/home'),
    13372067                                      JID('pubsub.example.org'), '')
     2068        d.addCallback(cb)
     2069        return d
     2070
     2071
     2072    def test_getDiscoInfoBadResponse(self):
     2073        """
     2074        If getInfo returns invalid response, it should be logged, then ignored.
     2075        """
     2076        def cb(info):
     2077            self.assertEquals([], info)
     2078            self.assertEqual(1, len(self.flushLoggedErrors(TypeError)))
     2079
     2080        def getInfo(requestor, target, nodeIdentifier):
     2081            return defer.succeed('bad response')
     2082
     2083        self.resource.getInfo = getInfo
     2084        d = self.service.getDiscoInfo(JID('user@example.org/home'),
     2085                                      JID('pubsub.example.org'), 'test')
     2086        d.addCallback(cb)
     2087        return d
     2088
     2089
     2090    def test_getDiscoInfoException(self):
     2091        """
     2092        If getInfo returns invalid response, it should be logged, then ignored.
     2093        """
     2094        def cb(info):
     2095            self.assertEquals([], info)
     2096            self.assertEqual(1, len(self.flushLoggedErrors(NotImplementedError)))
     2097
     2098        def getInfo(requestor, target, nodeIdentifier):
     2099            return defer.fail(NotImplementedError())
     2100
     2101        self.resource.getInfo = getInfo
     2102        d = self.service.getDiscoInfo(JID('user@example.org/home'),
     2103                                      JID('pubsub.example.org'), 'test')
    13382104        d.addCallback(cb)
    13392105        return d
     
    14892255
    14902256
     2257    def test_on_subscribeSubscriptionIdentifier(self):
     2258        """
     2259        If a subscription returns a subid, this should be available.
     2260        """
     2261
     2262        xml = """
     2263        <iq type='set' to='pubsub.example.org'
     2264                       from='user@example.org'>
     2265          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     2266            <subscribe node='test' jid='user@example.org/Home'/>
     2267          </pubsub>
     2268        </iq>
     2269        """
     2270
     2271        def subscribe(request):
     2272            subscription = pubsub.Subscription(request.nodeIdentifier,
     2273                                               request.subscriber,
     2274                                               'subscribed',
     2275                                               subscriptionIdentifier='1234')
     2276            return defer.succeed(subscription)
     2277
     2278        def cb(element):
     2279            self.assertEqual('1234', element.subscription.getAttribute('subid'))
     2280
     2281        self.resource.subscribe = subscribe
     2282        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     2283        d = self.handleRequest(xml)
     2284        d.addCallback(cb)
     2285        return d
     2286
     2287
    14912288    def test_on_unsubscribe(self):
    14922289        """
     
    15042301
    15052302        def unsubscribe(request):
     2303            return defer.succeed(None)
     2304
     2305        def cb(element):
     2306            self.assertIdentical(None, element)
     2307
     2308        self.resource.unsubscribe = unsubscribe
     2309        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     2310        d = self.handleRequest(xml)
     2311        d.addCallback(cb)
     2312        return d
     2313
     2314
     2315    def test_on_unsubscribeSubscriptionIdentifier(self):
     2316        """
     2317        A successful unsubscription with subid should return an empty response.
     2318        """
     2319
     2320        xml = """
     2321        <iq type='set' to='pubsub.example.org'
     2322                       from='user@example.org'>
     2323          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     2324            <unsubscribe node='test' jid='user@example.org/Home' subid='1234'/>
     2325          </pubsub>
     2326        </iq>
     2327        """
     2328
     2329        def unsubscribe(request):
     2330            self.assertEqual('1234', request.subscriptionIdentifier)
    15062331            return defer.succeed(None)
    15072332
     
    16022427            subscription = children[0]
    16032428            self.assertEqual('subscription', subscription.name)
    1604             self.assertEqual(NS_PUBSUB, subscription.uri)
     2429            self.assertEqual(NS_PUBSUB, subscription.uri, NS_PUBSUB)
    16052430            self.assertEqual('user@example.org', subscription['jid'])
    16062431            self.assertEqual('test', subscription['node'])
    16072432            self.assertEqual('subscribed', subscription['subscription'])
     2433
     2434        self.resource.subscriptions = subscriptions
     2435        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     2436        d = self.handleRequest(xml)
     2437        d.addCallback(cb)
     2438        return d
     2439
     2440
     2441    def test_on_subscriptionsWithSubscriptionIdentifier(self):
     2442        """
     2443        A subscriptions request response should include subids, if set.
     2444        """
     2445
     2446        xml = """
     2447        <iq type='get' to='pubsub.example.org'
     2448                       from='user@example.org'>
     2449          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     2450            <subscriptions/>
     2451          </pubsub>
     2452        </iq>
     2453        """
     2454
     2455        def subscriptions(request):
     2456            subscription = pubsub.Subscription('test', JID('user@example.org'),
     2457                                               'subscribed',
     2458                                               subscriptionIdentifier='1234')
     2459            return defer.succeed([subscription])
     2460
     2461        def cb(element):
     2462            subscription = element.subscriptions.subscription
     2463            self.assertEqual('1234', subscription['subid'])
    16082464
    16092465        self.resource.subscriptions = subscriptions
     
    17412597
    17422598
    1743     def test_on_default(self):
    1744         """
    1745         A default request should result in
    1746         L{PubSubService.getDefaultConfiguration} being called.
    1747         """
    1748 
    1749         xml = """
    1750         <iq type='get' to='pubsub.example.org'
    1751                        from='user@example.org'>
    1752           <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
    1753             <default/>
     2599    def test_on_createWithConfig(self):
     2600        """
     2601        On a node create with configuration request the Data Form is parsed and
     2602        L{PubSubResource.create} is called with the passed options.
     2603        """
     2604
     2605        xml = """
     2606        <iq type='set' to='pubsub.example.org'
     2607                       from='user@example.org'>
     2608          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     2609            <create node='mynode'/>
     2610            <configure>
     2611              <x xmlns='jabber:x:data' type='submit'>
     2612                <field var='FORM_TYPE' type='hidden'>
     2613                  <value>http://jabber.org/protocol/pubsub#node_config</value>
     2614                </field>
     2615                <field var='pubsub#deliver_payloads'><value>0</value></field>
     2616                <field var='pubsub#persist_items'><value>1</value></field>
     2617              </x>
     2618            </configure>
    17542619          </pubsub>
    17552620        </iq>
     
    17662631                }
    17672632
     2633        def create(request):
     2634            self.assertEqual({'pubsub#deliver_payloads': False,
     2635                              'pubsub#persist_items': True},
     2636                             request.options.getValues())
     2637            return defer.succeed(None)
     2638
     2639        self.resource.getConfigurationOptions = getConfigurationOptions
     2640        self.resource.create = create
     2641        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     2642        return self.handleRequest(xml)
     2643
     2644
     2645    def test_on_default(self):
     2646        """
     2647        A default request returns default options filtered by available fields.
     2648        """
     2649
     2650        xml = """
     2651        <iq type='get' to='pubsub.example.org'
     2652                       from='user@example.org'>
     2653          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     2654            <default/>
     2655          </pubsub>
     2656        </iq>
     2657        """
     2658        fieldDefs = {
     2659                "pubsub#persist_items":
     2660                    {"type": "boolean",
     2661                     "label": "Persist items to storage"},
     2662                "pubsub#deliver_payloads":
     2663                    {"type": "boolean",
     2664                     "label": "Deliver payloads with event notifications"}
     2665                }
     2666
     2667        def getConfigurationOptions():
     2668            return fieldDefs
     2669
    17682670        def default(request):
    1769             return defer.succeed({})
     2671            return defer.succeed({'pubsub#persist_items': 'false',
     2672                                  'x-myfield': '1'})
    17702673
    17712674        def cb(element):
    1772             self.assertEqual('pubsub', element.name)
    1773             self.assertEqual(NS_PUBSUB_OWNER, element.uri)
    1774             self.assertEqual(NS_PUBSUB_OWNER, element.default.uri)
     2675            self.assertEquals('pubsub', element.name)
     2676            self.assertEquals(NS_PUBSUB_OWNER, element.uri)
     2677            self.assertEquals(NS_PUBSUB_OWNER, element.default.uri)
    17752678            form = data_form.Form.fromElement(element.default.x)
    1776             self.assertEqual(NS_PUBSUB_CONFIG, form.formNamespace)
     2679            self.assertEquals(NS_PUBSUB_NODE_CONFIG, form.formNamespace)
     2680            form.typeCheck(fieldDefs)
     2681            self.assertIn('pubsub#persist_items', form.fields)
     2682            self.assertFalse(form.fields['pubsub#persist_items'].value)
     2683            self.assertNotIn('x-myfield', form.fields)
    17772684
    17782685        self.resource.getConfigurationOptions = getConfigurationOptions
     
    17842691
    17852692
    1786     def test_on_defaultCollection(self):
    1787         """
    1788         Responses to default requests should depend on passed node type.
    1789         """
    1790 
    1791         xml = """
    1792         <iq type='get' to='pubsub.example.org'
    1793                        from='user@example.org'>
    1794           <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
    1795             <default>
    1796               <x xmlns='jabber:x:data' type='submit'>
    1797                 <field var='FORM_TYPE' type='hidden'>
    1798                   <value>http://jabber.org/protocol/pubsub#node_config</value>
    1799                 </field>
    1800                 <field var='pubsub#node_type'>
    1801                   <value>collection</value>
    1802                 </field>
    1803               </x>
    1804             </default>
    1805 
    1806           </pubsub>
    1807         </iq>
    1808         """
    1809 
    1810         def getConfigurationOptions():
    1811             return {
    1812                 "pubsub#deliver_payloads":
    1813                     {"type": "boolean",
    1814                      "label": "Deliver payloads with event notifications"}
    1815                 }
    1816 
    1817         def default(request):
    1818             return defer.succeed({})
    1819 
    1820         self.resource.getConfigurationOptions = getConfigurationOptions
    1821         self.resource.default = default
    1822         verify.verifyObject(iwokkel.IPubSubResource, self.resource)
    1823         return self.handleRequest(xml)
    1824 
    1825 
    18262693    def test_on_defaultUnknownNodeType(self):
    18272694        """
    1828         A default request should result in
    1829         L{PubSubResource.default} being called.
     2695        Unknown node types yield non-acceptable.
     2696
     2697        Both C{getConfigurationOptions} and C{default} must not be called.
    18302698        """
    18312699
     
    18492717        """
    18502718
     2719        def getConfigurationOptions():
     2720            self.fail("Unexpected call to getConfigurationOptions")
     2721
    18512722        def default(request):
    1852             self.fail("Unexpected call to getConfiguration")
     2723            self.fail("Unexpected call to default")
    18532724
    18542725        def cb(result):
    18552726            self.assertEquals('not-acceptable', result.condition)
    18562727
     2728        self.resource.getConfigurationOptions = getConfigurationOptions
    18572729        self.resource.default = default
    18582730        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     
    18962768                                  'pubsub#persist_items': '1',
    18972769                                  'pubsub#owner': JID('user@example.org'),
    1898                                   'x-myfield': ['a', 'b']})
     2770                                  'x-myfield': 'a'})
    18992771
    19002772        def cb(element):
     
    19032775            self.assertEqual(NS_PUBSUB_OWNER, element.configure.uri)
    19042776            form = data_form.Form.fromElement(element.configure.x)
    1905             self.assertEqual(NS_PUBSUB_CONFIG, form.formNamespace)
     2777            self.assertEqual(NS_PUBSUB_NODE_CONFIG, form.formNamespace)
    19062778            fields = form.fields
    19072779
     
    19692841        def configureSet(request):
    19702842            self.assertEqual({'pubsub#deliver_payloads': False,
    1971                               'pubsub#persist_items': True}, request.options)
     2843                              'pubsub#persist_items': True},
     2844                             request.options.getValues())
    19722845            return defer.succeed(None)
    19732846
     
    20412914        def configureSet(request):
    20422915            self.assertEquals(['pubsub#deliver_payloads'],
    2043                               request.options.keys())
     2916                              request.options.fields.keys())
    20442917
    20452918        self.resource.getConfigurationOptions = getConfigurationOptions
     
    20732946        def cb(result):
    20742947            self.assertEquals('bad-request', result.condition)
     2948            self.assertEqual("Unexpected form type 'result'", result.text)
    20752949
    20762950        d = self.handleRequest(xml)
     
    21833057        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
    21843058        return self.handleRequest(xml)
     3059
     3060
     3061    def test_notifyPublish(self):
     3062        """
     3063        Publish notifications are sent to the subscribers.
     3064        """
     3065        subscriber = JID('user@example.org')
     3066        subscriptions = [pubsub.Subscription('test', subscriber, 'subscribed')]
     3067        items = [pubsub.Item('current')]
     3068        notifications = [(subscriber, subscriptions, items)]
     3069        self.service.notifyPublish(JID('pubsub.example.org'), 'test',
     3070                                   notifications)
     3071        message = self.stub.output[-1]
     3072
     3073        self.assertEquals('message', message.name)
     3074        self.assertIdentical(None, message.uri)
     3075        self.assertEquals('user@example.org', message['to'])
     3076        self.assertEquals('pubsub.example.org', message['from'])
     3077        self.assertTrue(message.event)
     3078        self.assertEquals(NS_PUBSUB_EVENT, message.event.uri)
     3079        self.assertTrue(message.event.items)
     3080        self.assertEquals(NS_PUBSUB_EVENT, message.event.items.uri)
     3081        self.assertTrue(message.event.items.hasAttribute('node'))
     3082        self.assertEquals('test', message.event.items['node'])
     3083        itemElements = list(domish.generateElementsQNamed(
     3084            message.event.items.children, 'item', NS_PUBSUB_EVENT))
     3085        self.assertEquals(1, len(itemElements))
     3086        self.assertEquals('current', itemElements[0].getAttribute('id'))
     3087
     3088
     3089    def test_notifyPublishCollection(self):
     3090        """
     3091        Publish notifications are sent to the subscribers of collections.
     3092
     3093        The node the item was published to is on the C{items} element, while
     3094        the subscribed-to node is in the C{'Collections'} SHIM header.
     3095        """
     3096        subscriber = JID('user@example.org')
     3097        subscriptions = [pubsub.Subscription('', subscriber, 'subscribed')]
     3098        items = [pubsub.Item('current')]
     3099        notifications = [(subscriber, subscriptions, items)]
     3100        self.service.notifyPublish(JID('pubsub.example.org'), 'test',
     3101                                   notifications)
     3102        message = self.stub.output[-1]
     3103
     3104        self.assertTrue(message.event.items.hasAttribute('node'))
     3105        self.assertEquals('test', message.event.items['node'])
     3106        headers = shim.extractHeaders(message)
     3107        self.assertIn('Collection', headers)
     3108        self.assertIn('', headers['Collection'])
    21853109
    21863110
     
    22883212    def test_on_affiliationsGet(self):
    22893213        """
    2290         Getting subscription options is not supported.
     3214        Getting node affiliations should have.
     3215        """
     3216
     3217        xml = """
     3218        <iq type='get' to='pubsub.example.org'
     3219                       from='user@example.org'>
     3220          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3221            <affiliations node='test'/>
     3222          </pubsub>
     3223        </iq>
     3224        """
     3225
     3226        def affiliationsGet(request):
     3227            self.assertEquals('test', request.nodeIdentifier)
     3228            return defer.succeed({JID('user@example.org'): 'owner'})
     3229
     3230        def cb(element):
     3231            self.assertEquals(u'pubsub', element.name)
     3232            self.assertEquals(NS_PUBSUB_OWNER, element.uri)
     3233            self.assertEquals(NS_PUBSUB_OWNER, element.affiliations.uri)
     3234            self.assertEquals(u'test', element.affiliations[u'node'])
     3235            children = list(element.affiliations.elements())
     3236            self.assertEquals(1, len(children))
     3237            affiliation = children[0]
     3238            self.assertEquals(u'affiliation', affiliation.name)
     3239            self.assertEquals(NS_PUBSUB_OWNER, affiliation.uri)
     3240            self.assertEquals(u'user@example.org', affiliation[u'jid'])
     3241            self.assertEquals(u'owner', affiliation[u'affiliation'])
     3242
     3243        self.resource.affiliationsGet = affiliationsGet
     3244        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     3245        d = self.handleRequest(xml)
     3246        d.addCallback(cb)
     3247        return d
     3248
     3249
     3250    def test_on_affiliationsGetEmptyNode(self):
     3251        """
     3252        Getting node affiliations without node should assume empty node.
    22913253        """
    22923254
     
    23003262        """
    23013263
     3264        def affiliationsGet(request):
     3265            self.assertIdentical('', request.nodeIdentifier)
     3266            return defer.succeed({})
     3267
     3268        def cb(element):
     3269            self.assertFalse(element.affiliations.hasAttribute(u'node'))
     3270
     3271        self.resource.affiliationsGet = affiliationsGet
     3272        verify.verifyObject(iwokkel.IPubSubResource, self.resource)
     3273        d = self.handleRequest(xml)
     3274        d.addCallback(cb)
     3275        return d
     3276
     3277
     3278    def test_on_affiliationsSet(self):
     3279        """
     3280        Setting node affiliations has the affiliations to be modified.
     3281        """
     3282
     3283        xml = """
     3284        <iq type='set' to='pubsub.example.org'
     3285                       from='user@example.org'>
     3286          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3287            <affiliations node='test'>
     3288              <affiliation jid='other@example.org' affiliation='publisher'/>
     3289            </affiliations>
     3290          </pubsub>
     3291        </iq>
     3292        """
     3293
     3294        def affiliationsSet(request):
     3295            self.assertEquals(u'test', request.nodeIdentifier)
     3296            otherJID = JID(u'other@example.org')
     3297            self.assertIn(otherJID, request.affiliations)
     3298            self.assertEquals(u'publisher', request.affiliations[otherJID])
     3299
     3300        self.resource.affiliationsSet = affiliationsSet
     3301        return self.handleRequest(xml)
     3302
     3303
     3304    def test_on_affiliationsSetBareJID(self):
     3305        """
     3306        Affiliations are always on the bare JID.
     3307        """
     3308
     3309        xml = """
     3310        <iq type='set' to='pubsub.example.org'
     3311                       from='user@example.org'>
     3312          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3313            <affiliations node='test'>
     3314              <affiliation jid='other@example.org/Home'
     3315                           affiliation='publisher'/>
     3316            </affiliations>
     3317          </pubsub>
     3318        </iq>
     3319        """
     3320
     3321        def affiliationsSet(request):
     3322            otherJID = JID(u'other@example.org')
     3323            self.assertIn(otherJID, request.affiliations)
     3324
     3325        self.resource.affiliationsSet = affiliationsSet
     3326        return self.handleRequest(xml)
     3327
     3328
     3329    def test_on_affiliationsSetMultipleForSameEntity(self):
     3330        """
     3331        Setting node affiliations can only have one item per entity.
     3332        """
     3333
     3334        xml = """
     3335        <iq type='set' to='pubsub.example.org'
     3336                       from='user@example.org'>
     3337          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3338            <affiliations node='test'>
     3339              <affiliation jid='other@example.org' affiliation='publisher'/>
     3340              <affiliation jid='other@example.org' affiliation='owner'/>
     3341            </affiliations>
     3342          </pubsub>
     3343        </iq>
     3344        """
     3345
    23023346        def cb(result):
    2303             self.assertEquals('feature-not-implemented', result.condition)
    2304             self.assertEquals('unsupported', result.appCondition.name)
    2305             self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
    2306             self.assertEquals('modify-affiliations',
    2307                               result.appCondition['feature'])
     3347            self.assertEquals('bad-request', result.condition)
    23083348
    23093349        d = self.handleRequest(xml)
     
    23133353
    23143354
    2315     def test_on_affiliationsSet(self):
    2316         """
    2317         Setting subscription options is not supported.
     3355    def test_on_affiliationsSetMissingJID(self):
     3356        """
     3357        Setting node affiliations must include a JID per affiliation.
    23183358        """
    23193359
     
    23223362                       from='user@example.org'>
    23233363          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
    2324             <affiliations/>
     3364            <affiliations node='test'>
     3365              <affiliation affiliation='publisher'/>
     3366            </affiliations>
    23253367          </pubsub>
    23263368        </iq>
     
    23283370
    23293371        def cb(result):
    2330             self.assertEquals('feature-not-implemented', result.condition)
    2331             self.assertEquals('unsupported', result.appCondition.name)
    2332             self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
    2333             self.assertEquals('modify-affiliations',
    2334                               result.appCondition['feature'])
     3372            self.assertEquals('bad-request', result.condition)
     3373
     3374        d = self.handleRequest(xml)
     3375        self.assertFailure(d, error.StanzaError)
     3376        d.addCallback(cb)
     3377        return d
     3378
     3379
     3380    def test_on_affiliationsSetMissingAffiliation(self):
     3381        """
     3382        Setting node affiliations must include an affiliation.
     3383        """
     3384
     3385        xml = """
     3386        <iq type='set' to='pubsub.example.org'
     3387                       from='user@example.org'>
     3388          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3389            <affiliations node='test'>
     3390              <affiliation jid='other@example.org'/>
     3391            </affiliations>
     3392          </pubsub>
     3393        </iq>
     3394        """
     3395
     3396        def cb(result):
     3397            self.assertEquals('bad-request', result.condition)
    23353398
    23363399        d = self.handleRequest(xml)
     
    23473410        self.service = pubsub.PubSubService()
    23483411        self.service.send = self.stub.xmlstream.send
     3412
     3413
     3414    def test_getDiscoInfo(self):
     3415        """
     3416        Test getDiscoInfo calls getNodeInfo and returns some minimal info.
     3417        """
     3418        def cb(info):
     3419            discoInfo = disco.DiscoInfo()
     3420            for item in info:
     3421                discoInfo.append(item)
     3422            self.assertIn(('pubsub', 'service'), discoInfo.identities)
     3423            self.assertIn(disco.NS_DISCO_ITEMS, discoInfo.features)
     3424
     3425        d = self.service.getDiscoInfo(JID('user@example.org/home'),
     3426                                      JID('pubsub.example.org'), '')
     3427        d.addCallback(cb)
     3428        return d
    23493429
    23503430
     
    25963676
    25973677
     3678    def test_setConfigurationOptionsDict(self):
     3679        """
     3680        Options should be passed as a dictionary, not a form.
     3681        """
     3682
     3683        xml = """
     3684        <iq type='set' to='pubsub.example.org'
     3685                       from='user@example.org'>
     3686          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3687            <configure node='test'>
     3688              <x xmlns='jabber:x:data' type='submit'>
     3689                <field var='FORM_TYPE' type='hidden'>
     3690                  <value>http://jabber.org/protocol/pubsub#node_config</value>
     3691                </field>
     3692                <field var='pubsub#deliver_payloads'><value>0</value></field>
     3693                <field var='pubsub#persist_items'><value>1</value></field>
     3694              </x>
     3695            </configure>
     3696          </pubsub>
     3697        </iq>
     3698        """
     3699
     3700        def getConfigurationOptions():
     3701            return {
     3702                "pubsub#persist_items":
     3703                    {"type": "boolean",
     3704                     "label": "Persist items to storage"},
     3705                "pubsub#deliver_payloads":
     3706                    {"type": "boolean",
     3707                     "label": "Deliver payloads with event notifications"}
     3708                }
     3709
     3710        def setConfiguration(requestor, service, nodeIdentifier, options):
     3711            self.assertEquals({'pubsub#deliver_payloads': False,
     3712                               'pubsub#persist_items': True}, options)
     3713
     3714
     3715        self.service.getConfigurationOptions = getConfigurationOptions
     3716        self.service.setConfiguration = setConfiguration
     3717        return self.handleRequest(xml)
     3718
     3719
    25983720    def test_items(self):
    25993721        """
     
    26923814            self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
    26933815            self.assertEquals('delete-nodes', result.appCondition['feature'])
     3816
     3817        d = self.handleRequest(xml)
     3818        self.assertFailure(d, error.StanzaError)
     3819        d.addCallback(cb)
     3820        return d
     3821
     3822
     3823    def test_unknown(self):
     3824        """
     3825        Unknown verb yields unsupported error.
     3826        """
     3827        xml = """
     3828        <iq type='get' to='pubsub.example.org'
     3829                       from='user@example.org'>
     3830          <pubsub xmlns='http://jabber.org/protocol/pubsub#owner'>
     3831            <affiliations node='test'/>
     3832          </pubsub>
     3833        </iq>
     3834        """
     3835
     3836        def cb(result):
     3837            self.assertEquals('feature-not-implemented', result.condition)
     3838            self.assertEquals('unsupported', result.appCondition.name)
     3839            self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
    26943840
    26953841        d = self.handleRequest(xml)
     
    29514097        d.addCallback(cb)
    29524098        return d
     4099
     4100
     4101    def test_affiliationsGet(self):
     4102        """
     4103        Non-overridden owner affiliations get yields unsupported error.
     4104        """
     4105
     4106        def cb(result):
     4107            self.assertEquals('feature-not-implemented', result.condition)
     4108            self.assertEquals('unsupported', result.appCondition.name)
     4109            self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
     4110            self.assertEquals('modify-affiliations',
     4111                              result.appCondition['feature'])
     4112
     4113        d = self.resource.affiliationsGet(pubsub.PubSubRequest())
     4114        self.assertFailure(d, error.StanzaError)
     4115        d.addCallback(cb)
     4116        return d
     4117
     4118
     4119    def test_affiliationsSet(self):
     4120        """
     4121        Non-overridden owner affiliations set yields unsupported error.
     4122        """
     4123
     4124        def cb(result):
     4125            self.assertEquals('feature-not-implemented', result.condition)
     4126            self.assertEquals('unsupported', result.appCondition.name)
     4127            self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri)
     4128            self.assertEquals('modify-affiliations',
     4129                              result.appCondition['feature'])
     4130
     4131        d = self.resource.affiliationsSet(pubsub.PubSubRequest())
     4132        self.assertFailure(d, error.StanzaError)
     4133        d.addCallback(cb)
     4134        return d
  • wokkel/test/test_server.py

    • Property exe set to *
    r55 r96  
    1 # Copyright (c) 2003-2008 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
  • wokkel/test/test_shim.py

    • Property exe set to *
    r27 r96  
    11# -*- test-case-name: wokkel.test.test_shim -*-
    22#
    3 # Copyright (c) 2003-2008 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
  • wokkel/test/test_subprotocols.py

    • Property exe set to *
    r45 r101  
    1 # Copyright (c) 2003-2007 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details.
    33
     
    1010from twisted.trial import unittest
    1111from twisted.test import proto_helpers
    12 from twisted.internet import defer
     12from twisted.internet import defer, task
     13from twisted.internet.error import ConnectionDone
     14from twisted.python import failure
    1315from twisted.words.xish import domish
    1416from twisted.words.protocols.jabber import error, xmlstream
    1517
    16 from wokkel import iwokkel, subprotocols
     18from wokkel import generic, iwokkel, subprotocols
    1719
    1820class DummyFactory(object):
     
    5658
    5759
     60class FailureReasonXMPPHandler(subprotocols.XMPPHandler):
     61    """
     62    Dummy handler specifically for failure Reason tests.
     63    """
     64    def __init__(self):
     65        self.gotFailureReason = False
     66
     67
     68    def connectionLost(self, reason):
     69        if isinstance(reason, failure.Failure):
     70            self.gotFailureReason = True
     71
     72
     73
     74class IQGetStanza(generic.Stanza):
     75    timeout = None
     76
     77    stanzaKind = 'iq'
     78    stanzaType = 'get'
     79    stanzaID = 'test'
     80
     81
     82
    5883class XMPPHandlerTest(unittest.TestCase):
    5984    """
     
    111136
    112137
     138    def test_request(self):
     139        """
     140        A request is passed up to the stream manager.
     141        """
     142        class DummyStreamManager(object):
     143            def __init__(self):
     144                self.requests = []
     145
     146            def request(self, request):
     147                self.requests.append(request)
     148                return defer.succeed(None)
     149
     150        handler = subprotocols.XMPPHandler()
     151        handler.parent = DummyStreamManager()
     152        request = IQGetStanza()
     153        d = handler.request(request)
     154        self.assertEquals(1, len(handler.parent.requests))
     155        self.assertIdentical(request, handler.parent.requests[-1])
     156        return d
     157
     158
    113159
    114160class XMPPHandlerCollectionTest(unittest.TestCase):
     
    156202
    157203    def setUp(self):
     204        factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
     205        self.clock = task.Clock()
     206        self.streamManager = subprotocols.StreamManager(factory, self.clock)
     207        self.xmlstream = factory.buildProtocol(None)
     208        self.transport = proto_helpers.StringTransport()
     209        self.xmlstream.transport = self.transport
     210
     211        self.request = IQGetStanza()
     212
     213    def _streamStarted(self):
     214        """
     215        Bring the test stream to the initialized state.
     216        """
     217        self.xmlstream.connectionMade()
     218        self.xmlstream.dataReceived(
     219                "<stream:stream xmlns='jabber:client' "
     220                    "xmlns:stream='http://etherx.jabber.org/streams' "
     221                    "from='example.com' id='12345'>")
     222        self.xmlstream.dispatch(self.xmlstream, "//event/stream/authd")
     223
     224
     225    def test_basic(self):
     226        """
     227        Test correct initialization and setup of factory observers.
     228        """
    158229        factory = DummyFactory()
    159         self.streamManager = subprotocols.StreamManager(factory)
    160 
    161     def test_basic(self):
    162         """
    163         Test correct initialization and setup of factory observers.
    164         """
    165         sm = self.streamManager
     230        sm = subprotocols.StreamManager(factory)
    166231        self.assertIdentical(None, sm.xmlstream)
    167232        self.assertEquals([], sm.handlers)
     
    235300    def test_disconnected(self):
    236301        """
    237         Test that protocol handlers have their connectionLost method
    238         called when the XML stream is disconnected.
    239         """
    240         sm = self.streamManager
    241         handler = DummyXMPPHandler()
    242         handler.setHandlerParent(sm)
    243         xs = xmlstream.XmlStream(xmlstream.Authenticator())
    244         sm._disconnected(xs)
     302        Protocol handlers have connectionLost called on stream disconnect.
     303        """
     304        sm = self.streamManager
     305        handler = DummyXMPPHandler()
     306        handler.setHandlerParent(sm)
     307        sm._disconnected(None)
    245308        self.assertEquals(0, handler.doneMade)
    246309        self.assertEquals(0, handler.doneInitialized)
     
    248311
    249312
     313    def test_disconnectedReason(self):
     314        """
     315        A L{STREAM_END_EVENT} results in L{StreamManager} firing the handlers
     316        L{connectionLost} methods, passing a L{failure.Failure} reason.
     317        """
     318        sm = self.streamManager
     319        handler = FailureReasonXMPPHandler()
     320        handler.setHandlerParent(sm)
     321        xs = xmlstream.XmlStream(xmlstream.Authenticator())
     322        sm._disconnected(failure.Failure(Exception("no reason")))
     323        self.assertEquals(True, handler.gotFailureReason)
     324
     325
    250326    def test_addHandler(self):
    251327        """
     
    261337
    262338
     339    def test_addHandlerConnected(self):
     340        """
     341        Adding a handler when connected doesn't call connectionInitialized.
     342        """
     343        sm = self.streamManager
     344        xs = xmlstream.XmlStream(xmlstream.Authenticator())
     345        sm._connected(xs)
     346        handler = DummyXMPPHandler()
     347        handler.setHandlerParent(sm)
     348
     349        self.assertEquals(1, handler.doneMade)
     350        self.assertEquals(0, handler.doneInitialized)
     351        self.assertEquals(0, handler.doneLost)
     352
     353
     354    def test_addHandlerConnectedNested(self):
     355        """
     356        Adding a handler in connectionMade doesn't cause 2nd call.
     357        """
     358        class NestingHandler(DummyXMPPHandler):
     359            nestedHandler = None
     360
     361            def connectionMade(self):
     362                DummyXMPPHandler.connectionMade(self)
     363                self.nestedHandler = DummyXMPPHandler()
     364                self.nestedHandler.setHandlerParent(self.parent)
     365
     366        sm = self.streamManager
     367        xs = xmlstream.XmlStream(xmlstream.Authenticator())
     368        handler = NestingHandler()
     369        handler.setHandlerParent(sm)
     370        sm._connected(xs)
     371
     372        self.assertEquals(1, handler.doneMade)
     373        self.assertEquals(0, handler.doneInitialized)
     374        self.assertEquals(0, handler.doneLost)
     375
     376        self.assertEquals(1, handler.nestedHandler.doneMade)
     377        self.assertEquals(0, handler.nestedHandler.doneInitialized)
     378        self.assertEquals(0, handler.nestedHandler.doneLost)
     379
     380
     381
    263382    def test_addHandlerInitialized(self):
    264383        """
     
    281400        self.assertEquals(0, handler.doneLost)
    282401
     402
     403    def test_addHandlerInitializedNested(self):
     404        """
     405        Adding a handler in connectionInitialized doesn't cause 2nd call.
     406        """
     407        class NestingHandler(DummyXMPPHandler):
     408            nestedHandler = None
     409
     410            def connectionInitialized(self):
     411                DummyXMPPHandler.connectionInitialized(self)
     412                self.nestedHandler = DummyXMPPHandler()
     413                self.nestedHandler.setHandlerParent(self.parent)
     414
     415        sm = self.streamManager
     416        xs = xmlstream.XmlStream(xmlstream.Authenticator())
     417        handler = NestingHandler()
     418        handler.setHandlerParent(sm)
     419        sm._connected(xs)
     420        sm._authd(xs)
     421
     422        self.assertEquals(1, handler.doneMade)
     423        self.assertEquals(1, handler.doneInitialized)
     424        self.assertEquals(0, handler.doneLost)
     425
     426        self.assertEquals(1, handler.nestedHandler.doneMade)
     427        self.assertEquals(1, handler.nestedHandler.doneInitialized)
     428        self.assertEquals(0, handler.nestedHandler.doneLost)
     429
     430
     431    def test_addHandlerConnectionLostNested(self):
     432        """
     433        Adding a handler in connectionLost doesn't call connectionLost there.
     434        """
     435        class NestingHandler(DummyXMPPHandler):
     436            nestedHandler = None
     437
     438            def connectionLost(self, reason):
     439                DummyXMPPHandler.connectionLost(self, reason)
     440                self.nestedHandler = DummyXMPPHandler()
     441                self.nestedHandler.setHandlerParent(self.parent)
     442
     443        sm = self.streamManager
     444        xs = xmlstream.XmlStream(xmlstream.Authenticator())
     445        handler = NestingHandler()
     446        handler.setHandlerParent(sm)
     447        sm._connected(xs)
     448        sm._authd(xs)
     449        sm._disconnected(xs)
     450
     451        self.assertEquals(1, handler.doneMade)
     452        self.assertEquals(1, handler.doneInitialized)
     453        self.assertEquals(1, handler.doneLost)
     454
     455        self.assertEquals(0, handler.nestedHandler.doneMade)
     456        self.assertEquals(0, handler.nestedHandler.doneInitialized)
     457        self.assertEquals(0, handler.nestedHandler.doneLost)
     458
     459
     460
    283461    def test_removeHandler(self):
    284462        """
     
    291469        self.assertNotIn(handler, sm)
    292470        self.assertIdentical(None, handler.parent)
     471
    293472
    294473    def test_sendInitialized(self):
     
    381560        self.assertEquals("", xs.transport.value())
    382561        self.assertEquals("<presence/>", sm._packetQueue[0])
     562
     563
     564    def test_requestSendInitialized(self):
     565        """
     566        A request is sent out over the wire when the stream is initialized.
     567        """
     568        self._streamStarted()
     569
     570        self.streamManager.request(self.request)
     571        expected = u"<iq type='get' id='%s'/>" % self.request.stanzaID
     572        self.assertEquals(expected, self.transport.value())
     573
     574
     575    def test_requestSendInitializedFreshID(self):
     576        """
     577        A request without an ID gets a fresh one upon send.
     578        """
     579        self._streamStarted()
     580
     581        self.request.stanzaID = None
     582        self.streamManager.request(self.request)
     583        self.assertNotIdentical(None, self.request.stanzaID)
     584        expected = u"<iq type='get' id='%s'/>" % self.request.stanzaID
     585        self.assertEquals(expected, self.transport.value())
     586
     587
     588    def test_requestSendNotConnected(self):
     589        """
     590        A request is queued until a stream is initialized.
     591        """
     592        handler = DummyXMPPHandler()
     593        self.streamManager.addHandler(handler)
     594
     595        self.streamManager.request(self.request)
     596        expected = u"<iq type='get' id='test'/>"
     597
     598        xs = self.xmlstream
     599        self.assertEquals("", xs.transport.value())
     600
     601        xs.connectionMade()
     602        self.assertEquals("", xs.transport.value())
     603
     604        xs.dataReceived("<stream:stream xmlns='jabber:client' "
     605                        "xmlns:stream='http://etherx.jabber.org/streams' "
     606                        "from='example.com' id='12345'>")
     607        xs.dispatch(xs, "//event/stream/authd")
     608
     609        self.assertEquals(expected, xs.transport.value())
     610        self.assertFalse(self.streamManager._packetQueue)
     611
     612
     613    def test_requestResultResponse(self):
     614        """
     615        A result response gets the request deferred fired with the response.
     616        """
     617        def cb(result):
     618            self.assertEquals(result['type'], 'result')
     619
     620        self._streamStarted()
     621        d = self.streamManager.request(self.request)
     622        d.addCallback(cb)
     623
     624        xs = self.xmlstream
     625        xs.dataReceived("<iq type='result' id='test'/>")
     626        return d
     627
     628
     629    def test_requestErrorResponse(self):
     630        """
     631        An error response gets the request deferred fired with a failure.
     632        """
     633        self._streamStarted()
     634        d = self.streamManager.request(self.request)
     635        self.assertFailure(d, error.StanzaError)
     636
     637        xs = self.xmlstream
     638        xs.dataReceived("<iq type='error' id='test'/>")
     639        return d
     640
     641
     642    def test_requestNonTrackedResponse(self):
     643        """
     644        Test that untracked iq responses don't trigger any action.
     645
     646        Untracked means that the id of the incoming response iq is not
     647        in the stream's C{iqDeferreds} dictionary.
     648        """
     649        # Set up a fallback handler that checks the stanza's handled attribute.
     650        # If that is set to True, the iq tracker claims to have handled the
     651        # response.
     652        dispatched = []
     653        def cb(iq):
     654            dispatched.append(iq)
     655
     656        self._streamStarted()
     657        self.xmlstream.addObserver("/iq", cb, -1)
     658
     659        # Receive an untracked iq response
     660        self.xmlstream.dataReceived("<iq type='result' id='other'/>")
     661        self.assertEquals(1, len(dispatched))
     662        self.assertFalse(getattr(dispatched[-1], 'handled', False))
     663
     664
     665    def test_requestCleanup(self):
     666        """
     667        Test if the deferred associated with an iq request is removed
     668        from the list kept in the L{XmlStream} object after it has
     669        been fired.
     670        """
     671        self._streamStarted()
     672        d = self.streamManager.request(self.request)
     673        xs = self.xmlstream
     674        xs.dataReceived("<iq type='result' id='test'/>")
     675        self.assertNotIn('test', self.streamManager._iqDeferreds)
     676        return d
     677
     678
     679    def test_requestDisconnectCleanup(self):
     680        """
     681        Test if deferreds for iq's that haven't yet received a response
     682        have their errback called on stream disconnect.
     683        """
     684        d = self.streamManager.request(self.request)
     685        xs = self.xmlstream
     686        xs.connectionLost(failure.Failure(ConnectionDone()))
     687        self.assertFailure(d, ConnectionDone)
     688        return d
     689
     690
     691    def test_requestNoModifyingDict(self):
     692        """
     693        Test to make sure the errbacks cannot cause the iteration of the
     694        iqDeferreds to blow up in our face.
     695        """
     696
     697        def eb(failure):
     698            d = xmlstream.IQ(self.xmlstream).send()
     699            d.addErrback(eb)
     700
     701        d = self.streamManager.request(self.request)
     702        d.addErrback(eb)
     703        self.xmlstream.connectionLost(failure.Failure(ConnectionDone()))
     704        return d
     705
     706
     707    def test_requestTimingOut(self):
     708        """
     709        Test that an iq request with a defined timeout times out.
     710        """
     711        self.request.timeout = 60
     712        d = self.streamManager.request(self.request)
     713        self.assertFailure(d, xmlstream.TimeoutError)
     714
     715        self.clock.pump([1, 60])
     716        self.assertFalse(self.clock.calls)
     717        self.assertFalse(self.streamManager._iqDeferreds)
     718        return d
     719
     720
     721    def test_requestNotTimingOut(self):
     722        """
     723        Test that an iq request with a defined timeout does not time out
     724        when a response was received before the timeout period elapsed.
     725        """
     726        self._streamStarted()
     727        self.request.timeout = 60
     728        d = self.streamManager.request(self.request)
     729        self.clock.callLater(1, self.xmlstream.dataReceived,
     730                             "<iq type='result' id='test'/>")
     731        self.clock.pump([1, 1])
     732        self.assertFalse(self.clock.calls)
     733        return d
     734
     735
     736    def test_requestDisconnectTimeoutCancellation(self):
     737        """
     738        Test if timeouts for iq's that haven't yet received a response
     739        are cancelled on stream disconnect.
     740        """
     741
     742        self.request.timeout = 60
     743        d = self.streamManager.request(self.request)
     744
     745        self.xmlstream.connectionLost(failure.Failure(ConnectionDone()))
     746        self.assertFailure(d, ConnectionDone)
     747        self.assertFalse(self.clock.calls)
     748        return d
     749
     750
     751    def test_requestNotIQ(self):
     752        """
     753        The request stanza must be an iq.
     754        """
     755        stanza = generic.Stanza()
     756        stanza.stanzaKind = 'message'
     757
     758        d = self.streamManager.request(stanza)
     759        self.assertFailure(d, ValueError)
     760
     761
     762    def test_requestNotResult(self):
     763        """
     764        The request stanza cannot be of type 'result'.
     765        """
     766        stanza = generic.Stanza()
     767        stanza.stanzaKind = 'iq'
     768        stanza.stanzaType = 'result'
     769
     770        d = self.streamManager.request(stanza)
     771        self.assertFailure(d, ValueError)
     772
     773
     774    def test_requestNotError(self):
     775        """
     776        The request stanza cannot be of type 'error'.
     777        """
     778        stanza = generic.Stanza()
     779        stanza.stanzaKind = 'iq'
     780        stanza.stanzaType = 'error'
     781
     782        d = self.streamManager.request(stanza)
     783        self.assertFailure(d, ValueError)
    383784
    384785
  • wokkel/test/test_xmppim.py

    r68 r96  
    1 # Copyright (c) 2003-2009 Ralph Meijer
     1# Copyright (c) Ralph Meijer.
    22# See LICENSE for details
    33
  • wokkel/xmppim.py

    • Property exe set to *
    r68 r96  
    11# -*- test-case-name: wokkel.test.test_xmppim -*-
    22#
    3 # Copyright (c) 2003-2009 Ralph Meijer
     3# Copyright (c) Ralph Meijer.
    44# See LICENSE for details.
    55
Note: See TracChangeset for help on using the changeset viewer.