source: ralphm-patches/pubsub-subscription-options.patch @ 27:a9704141033a

Last change on this file since 27:a9704141033a was 27:a9704141033a, checked in by Ralph Meijer <ralphm@…>, 11 years ago

Ensure all modified code has unittests, add setOptions to PubSubClient?.

File size: 13.5 KB
  • wokkel/pubsub.py

    Add support for subscription options in subscribe requests
    
    diff -r 0555c117c086 wokkel/pubsub.py
    a b  
    228228    # Map request verb to parameter handler names
    229229    _parameters = {
    230230        'publish': ['node', 'items'],
    231         'subscribe': ['nodeOrEmpty', 'jid'],
     231        'subscribe': ['nodeOrEmpty', 'jid', 'optionsWithSubscribe'],
    232232        'unsubscribe': ['nodeOrEmpty', 'jid'],
    233233        'optionsGet': ['nodeOrEmpty', 'jid'],
    234234        'optionsSet': ['nodeOrEmpty', 'jid', 'options'],
     
    372372            if element.uri == NS_PUBSUB and element.name == 'configure':
    373373                form = data_form.findForm(element, NS_PUBSUB_NODE_CONFIG)
    374374                if form:
    375                     if form.formType == 'submit':
    376                         self.options = form
    377                     else:
     375                    if form.formType != 'submit':
    378376                        raise BadRequest(text=u"Unexpected form type '%s'" %
    379377                                              form.formType)
    380378                else:
    381379                    form = data_form.Form('submit',
    382380                                          formNamespace=NS_PUBSUB_NODE_CONFIG)
    383                     self.options = form
     381                self.options = form
    384382
    385383
    386384    def _render_configureOrNone(self, verbElement):
     
    451449            raise BadRequest(text="Missing options form")
    452450
    453451
     452
     453    def _render_options(self, verbElement):
     454        verbElement.addChild(self.options.toElement())
     455
     456
     457    def _parse_optionsWithSubscribe(self, verbElement):
     458        for element in verbElement.parent.elements():
     459            if element.name == 'options' and element.uri == NS_PUBSUB:
     460                form = data_form.findForm(element,
     461                                          NS_PUBSUB_SUBSCRIBE_OPTIONS)
     462                if form:
     463                    if form.formType != 'submit':
     464                        raise BadRequest(text=u"Unexpected form type '%s'" %
     465                                              form.formType)
     466                else:
     467                    form = data_form.Form('submit',
     468                                          formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     469                self.options = form
     470
     471
     472    def _render_optionsWithSubscribe(self, verbElement):
     473        if self.options:
     474            optionsElement = verbElement.parent.addElement('options')
     475            self._render_options(optionsElement)
     476
     477
    454478    def parseElement(self, element):
    455479        """
    456480        Parse the publish-subscribe verb and parameters out of a request.
    457481        """
    458482        generic.Stanza.parseElement(self, element)
    459483
     484        verbs = []
     485        children = []
    460486        for child in element.pubsub.elements():
    461487            key = (self.stanzaType, child.uri, child.name)
    462488            try:
    463489                verb = self._requestVerbMap[key]
    464490            except KeyError:
    465491                continue
    466             else:
    467                 self.verb = verb
    468                 break
    469492
    470         if not self.verb:
     493            verbs.append(verb)
     494            children.append(child)
     495
     496        if not verbs:
    471497            raise NotImplementedError()
    472498
    473         for parameter in self._parameters[verb]:
     499        if len(verbs) > 1:
     500            if 'optionsSet' in verbs and 'subscribe' in verbs:
     501                self.verb = 'subscribe'
     502                child = children[verbs.index('subscribe')]
     503            else:
     504                raise NotImplementedError()
     505        else:
     506            self.verb = verbs[0]
     507
     508        for parameter in self._parameters[self.verb]:
    474509            getattr(self, '_parse_%s' % parameter)(child)
    475510
    476511
     512
    477513    def send(self, xs):
    478514        """
    479515        Send this request to its recipient.
     
    686722        return request.send(self.xmlstream)
    687723
    688724
    689     def subscribe(self, service, nodeIdentifier, subscriber, sender=None):
     725    def subscribe(self, service, nodeIdentifier, subscriber,
     726                        options=None, sender=None):
    690727        """
    691728        Subscribe to a publish subscribe node.
    692729
     
    697734        @param subscriber: The entity to subscribe to the node. This entity
    698735                           will get notifications of new published items.
    699736        @type subscriber: L{JID}
     737        @param options: Subscription options.
     738        @type options: C{dict}.
    700739        """
    701740        request = PubSubRequest('subscribe')
    702741        request.recipient = service
     
    704743        request.subscriber = subscriber
    705744        request.sender = sender
    706745
     746        if options:
     747            form = data_form.Form(formType='submit',
     748                                  formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     749            form.makeFields(options)
     750            request.options = form
     751
    707752        def cb(iq):
    708753            subscription = iq.pubsub.subscription["subscription"]
    709754
     
    790835        return d
    791836
    792837
     838    def setOptions(self, service, nodeIdentifier, subscriber,
     839                         options, sender=None):
     840        """
     841        Set subscription options.
     842
     843        @param service: The publish subscribe service that keeps the node.
     844        @type service: L{JID}
     845
     846        @param nodeIdentifier: The identifier of the node.
     847        @type nodeIdentifier: C{unicode}
     848
     849        @param subscriber: The entity subscribed to the node.
     850        @type subscriber: L{JID}
     851
     852        @param options: Subscription options.
     853        @type options: C{dict}.
     854        """
     855        request = PubSubRequest('optionsSet')
     856        request.recipient = service
     857        request.nodeIdentifier = nodeIdentifier
     858        request.subscriber = subscriber
     859        request.sender = sender
     860
     861        form = data_form.Form(formType='submit',
     862                              formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS)
     863        form.makeFields(options)
     864        request.options = form
     865
     866        d = request.send(self.xmlstream)
     867        return d
     868
     869
    793870
    794871class PubSubService(XMPPHandler, IQHandlerMixin):
    795872    """
  • wokkel/test/test_pubsub.py

    diff -r 0555c117c086 wokkel/test/test_pubsub.py
    a b  
    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):
    2930    """
     
    481482        return d
    482483
    483484
     485    def test_subscribeWithOptions(self):
     486        options = {'pubsub#deliver': False}
     487
     488        d = self.protocol.subscribe(JID('pubsub.example.org'), 'test',
     489                                    JID('user@example.org'),
     490                                    options=options)
     491        iq = self.stub.output[-1]
     492
     493        # Check options present
     494        childNames = []
     495        for element in iq.pubsub.elements():
     496            if element.uri == NS_PUBSUB:
     497                childNames.append(element.name)
     498
     499        self.assertEqual(['subscribe', 'options'], childNames)
     500        form = data_form.findForm(iq.pubsub.options,
     501                                  NS_PUBSUB_SUBSCRIBE_OPTIONS)
     502        self.assertEqual('submit', form.formType)
     503        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     504        self.assertEqual(options, form.getValues())
     505
     506        # Send response
     507        response = toResponse(iq, 'result')
     508        pubsub = response.addElement((NS_PUBSUB, 'pubsub'))
     509        subscription = pubsub.addElement('subscription')
     510        subscription['node'] = 'test'
     511        subscription['jid'] = 'user@example.org'
     512        subscription['subscription'] = 'subscribed'
     513        self.stub.send(response)
     514
     515        return d
     516
     517
    484518    def test_subscribeWithSender(self):
    485519        """
    486520        Test sending subscription request from a specific JID.
     
    624658        return d
    625659
    626660
     661    def test_setOptions(self):
     662        options = {'pubsub#deliver': False}
     663
     664        d = self.protocol.setOptions(JID('pubsub.example.org'), 'test',
     665                                     JID('user@example.org'),
     666                                     options,
     667                                     sender=JID('user@example.org'))
     668
     669        iq = self.stub.output[-1]
     670        self.assertEqual('pubsub.example.org', iq.getAttribute('to'))
     671        self.assertEqual('set', iq.getAttribute('type'))
     672        self.assertEqual('pubsub', iq.pubsub.name)
     673        self.assertEqual(NS_PUBSUB, iq.pubsub.uri)
     674        children = list(domish.generateElementsQNamed(iq.pubsub.children,
     675                                                      'options', NS_PUBSUB))
     676        self.assertEqual(1, len(children))
     677        child = children[0]
     678        self.assertEqual('test', child['node'])
     679
     680        form = data_form.findForm(child, NS_PUBSUB_SUBSCRIBE_OPTIONS)
     681        self.assertEqual('submit', form.formType)
     682        form.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     683        self.assertEqual(options, form.getValues())
     684
     685        response = toResponse(iq, 'result')
     686        self.stub.send(response)
     687
     688        return d
     689
    627690
    628691class PubSubRequestTest(unittest.TestCase):
    629692
     693    def test_fromElementUnknown(self):
     694        """
     695        An unknown verb raises NotImplementedError.
     696        """
     697
     698        xml = """
     699        <iq type='set' to='pubsub.example.org'
     700                       from='user@example.org'>
     701          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     702            <non-existing-verb/>
     703          </pubsub>
     704        </iq>
     705        """
     706
     707        self.assertRaises(NotImplementedError,
     708                          pubsub.PubSubRequest.fromElement, parseXml(xml))
     709
     710
     711    def test_fromElementKnownBadCombination(self):
     712        """
     713        Multiple verbs in an unknown configuration raises NotImplementedError.
     714        """
     715
     716        xml = """
     717        <iq type='set' to='pubsub.example.org'
     718                       from='user@example.org'>
     719          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     720             <publish/>
     721             <create/>
     722          </pubsub>
     723        </iq>
     724        """
     725
     726        self.assertRaises(NotImplementedError,
     727                          pubsub.PubSubRequest.fromElement, parseXml(xml))
     728
    630729    def test_fromElementPublish(self):
    631730        """
    632731        Test parsing a publish request.
     
    752851        self.assertEqual(NS_PUBSUB_ERRORS, err.appCondition.uri)
    753852        self.assertEqual('jid-required', err.appCondition.name)
    754853
     854
     855    def test_fromElementSubscribeWithOptions(self):
     856        """
     857        Test parsing a subscription request.
     858        """
     859
     860        xml = """
     861        <iq type='set' to='pubsub.example.org'
     862                       from='user@example.org'>
     863          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     864            <subscribe node='test' jid='user@example.org/Home'/>
     865            <options>
     866              <x xmlns="jabber:x:data" type='submit'>
     867                <field var='FORM_TYPE' type='hidden'>
     868                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     869                </field>
     870                <field var='pubsub#deliver' type='boolean'
     871                       label='Enable delivery?'>
     872                  <value>1</value>
     873                </field>
     874              </x>
     875            </options>
     876          </pubsub>
     877        </iq>
     878        """
     879
     880        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     881        self.assertEqual('subscribe', request.verb)
     882        request.options.typeCheck({'pubsub#deliver': {'type': 'boolean'}})
     883        self.assertEqual({'pubsub#deliver': True}, request.options.getValues())
     884
     885
     886    def test_fromElementSubscribeWithOptionsBadFormType(self):
     887        """
     888        The options form should have the right type.
     889        """
     890
     891        xml = """
     892        <iq type='set' to='pubsub.example.org'
     893                       from='user@example.org'>
     894          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     895            <subscribe node='test' jid='user@example.org/Home'/>
     896            <options>
     897              <x xmlns="jabber:x:data" type='result'>
     898                <field var='FORM_TYPE' type='hidden'>
     899                  <value>http://jabber.org/protocol/pubsub#subscribe_options</value>
     900                </field>
     901                <field var='pubsub#deliver' type='boolean'
     902                       label='Enable delivery?'>
     903                  <value>1</value>
     904                </field>
     905              </x>
     906            </options>
     907          </pubsub>
     908        </iq>
     909        """
     910
     911        err = self.assertRaises(error.StanzaError,
     912                                pubsub.PubSubRequest.fromElement,
     913                                parseXml(xml))
     914        self.assertEqual('bad-request', err.condition)
     915        self.assertEqual("Unexpected form type 'result'", err.text)
     916        self.assertEqual(None, err.appCondition)
     917
     918
     919    def test_fromElementSubscribeWithOptionsEmpty(self):
     920        """
     921        When no (suitable) form is found, the options are empty.
     922        """
     923
     924        xml = """
     925        <iq type='set' to='pubsub.example.org'
     926                       from='user@example.org'>
     927          <pubsub xmlns='http://jabber.org/protocol/pubsub'>
     928            <subscribe node='test' jid='user@example.org/Home'/>
     929            <options/>
     930          </pubsub>
     931        </iq>
     932        """
     933
     934        request = pubsub.PubSubRequest.fromElement(parseXml(xml))
     935        self.assertEqual('subscribe', request.verb)
     936        self.assertEqual({}, request.options.getValues())
     937
     938
    755939    def test_fromElementUnsubscribe(self):
    756940        """
    757941        Test parsing an unsubscription request.
Note: See TracBrowser for help on using the repository browser.