# HG changeset patch # User souliane # Date 1436880122 -7200 # Node ID 2cd4f0ab9ad1d1da41cf451170ab12ea9c1487c6 # Parent 9a4c367b22bbd3f09589badb21cc9d7492785718 add tests for sat.tmp.wokkel diff -r 9a4c367b22bb -r 2cd4f0ab9ad1 wokkel/test/__init__.py diff -r 9a4c367b22bb -r 2cd4f0ab9ad1 wokkel/test/test_pubsub.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wokkel/test/test_pubsub.py Tue Jul 14 15:22:02 2015 +0200 @@ -0,0 +1,4218 @@ +# Copyright (c) Ralph Meijer. +# See LICENSE for details. + +""" +Tests for L{wokkel.pubsub} +""" + +from zope.interface import verify + +from twisted.trial import unittest +from twisted.internet import defer +from twisted.words.xish import domish +from twisted.words.protocols.jabber import error +from twisted.words.protocols.jabber.jid import JID +from twisted.words.protocols.jabber.xmlstream import toResponse + +from wokkel import data_form, disco, iwokkel, shim +from wokkel.generic import parseXml +from wokkel.test.helpers import TestableRequestHandlerMixin, XmlStreamStub + +from sat.tmp.wokkel import pubsub + +NS_PUBSUB = 'http://jabber.org/protocol/pubsub' +NS_PUBSUB_NODE_CONFIG = 'http://jabber.org/protocol/pubsub#node_config' +NS_PUBSUB_ERRORS = 'http://jabber.org/protocol/pubsub#errors' +NS_PUBSUB_EVENT = 'http://jabber.org/protocol/pubsub#event' +NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner' +NS_PUBSUB_META_DATA = 'http://jabber.org/protocol/pubsub#meta-data' +NS_PUBSUB_SUBSCRIBE_OPTIONS = 'http://jabber.org/protocol/pubsub#subscribe_options' + +def calledAsync(fn): + """ + Function wrapper that fires a deferred upon calling the given function. + """ + d = defer.Deferred() + + def func(*args, **kwargs): + try: + result = fn(*args, **kwargs) + except: + d.errback() + else: + d.callback(result) + + return d, func + + +class SubscriptionTest(unittest.TestCase): + """ + Tests for L{pubsub.Subscription}. + """ + + def test_fromElement(self): + """ + fromElement parses a subscription from XML DOM. + """ + xml = """ + + """ + subscription = pubsub.Subscription.fromElement(parseXml(xml)) + self.assertEqual('test', subscription.nodeIdentifier) + self.assertEqual(JID('user@example.org/Home'), subscription.subscriber) + self.assertEqual('pending', subscription.state) + self.assertIdentical(None, subscription.subscriptionIdentifier) + + + def test_fromElementWithSubscriptionIdentifier(self): + """ + A subscription identifier in the subscription should be parsed, too. + """ + xml = """ + + """ + subscription = pubsub.Subscription.fromElement(parseXml(xml)) + self.assertEqual('1234', subscription.subscriptionIdentifier) + + + def test_toElement(self): + """ + Rendering a Subscription should yield the proper attributes. + """ + subscription = pubsub.Subscription('test', + JID('user@example.org/Home'), + 'pending') + element = subscription.toElement() + self.assertEqual('subscription', element.name) + self.assertEqual(None, element.uri) + self.assertEqual('test', element.getAttribute('node')) + self.assertEqual('user@example.org/Home', element.getAttribute('jid')) + self.assertEqual('pending', element.getAttribute('subscription')) + self.assertFalse(element.hasAttribute('subid')) + + + def test_toElementEmptyNodeIdentifier(self): + """ + The empty node identifier should not yield a node attribute. + """ + subscription = pubsub.Subscription('', + JID('user@example.org/Home'), + 'pending') + element = subscription.toElement() + self.assertFalse(element.hasAttribute('node')) + + + def test_toElementWithSubscriptionIdentifier(self): + """ + The subscription identifier, if set, is in the subid attribute. + """ + subscription = pubsub.Subscription('test', + JID('user@example.org/Home'), + 'pending', + subscriptionIdentifier='1234') + element = subscription.toElement() + self.assertEqual('1234', element.getAttribute('subid')) + + + +class PubSubClientTest(unittest.TestCase): + timeout = 2 + + def setUp(self): + self.stub = XmlStreamStub() + self.protocol = pubsub.PubSubClient() + self.protocol.xmlstream = self.stub.xmlstream + self.protocol.connectionInitialized() + + + def test_interface(self): + """ + Do instances of L{pubsub.PubSubClient} provide L{iwokkel.IPubSubClient}? + """ + verify.verifyObject(iwokkel.IPubSubClient, self.protocol) + + + def test_eventItems(self): + """ + Test receiving an items event resulting in a call to itemsReceived. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + items = event.addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item1' + item2 = items.addElement('retract') + item2['id'] = 'item2' + item3 = items.addElement('item') + item3['id'] = 'item3' + + def itemsReceived(event): + self.assertEquals(JID('user@example.org/home'), event.recipient) + self.assertEquals(JID('pubsub.example.org'), event.sender) + self.assertEquals('test', event.nodeIdentifier) + self.assertEquals([item1, item2, item3], event.items) + + d, self.protocol.itemsReceived = calledAsync(itemsReceived) + self.stub.send(message) + return d + + + def test_eventItemsCollection(self): + """ + Test receiving an items event resulting in a call to itemsReceived. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + items = event.addElement('items') + items['node'] = 'test' + + headers = shim.Headers([('Collection', 'collection')]) + message.addChild(headers) + + def itemsReceived(event): + self.assertEquals(JID('user@example.org/home'), event.recipient) + self.assertEquals(JID('pubsub.example.org'), event.sender) + self.assertEquals('test', event.nodeIdentifier) + self.assertEquals({'Collection': ['collection']}, event.headers) + + d, self.protocol.itemsReceived = calledAsync(itemsReceived) + self.stub.send(message) + return d + + + def test_eventItemsError(self): + """ + An error message with embedded event should not be handled. + + This test uses an items event, which should not result in itemsReceived + being called. In general message.handled should be False. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + message['type'] = 'error' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + items = event.addElement('items') + items['node'] = 'test' + + class UnexpectedCall(Exception): + pass + + def itemsReceived(event): + raise UnexpectedCall("Unexpected call to itemsReceived") + + self.protocol.itemsReceived = itemsReceived + self.stub.send(message) + self.assertFalse(message.handled) + + + def test_eventDelete(self): + """ + Test receiving a delete event resulting in a call to deleteReceived. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + delete = event.addElement('delete') + delete['node'] = 'test' + + def deleteReceived(event): + self.assertEquals(JID('user@example.org/home'), event.recipient) + self.assertEquals(JID('pubsub.example.org'), event.sender) + self.assertEquals('test', event.nodeIdentifier) + + d, self.protocol.deleteReceived = calledAsync(deleteReceived) + self.stub.send(message) + return d + + + def test_eventDeleteRedirect(self): + """ + Test receiving a delete event with a redirect URI. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + delete = event.addElement('delete') + delete['node'] = 'test' + uri = 'xmpp:pubsub.example.org?;node=test2' + delete.addElement('redirect')['uri'] = uri + + def deleteReceived(event): + self.assertEquals(JID('user@example.org/home'), event.recipient) + self.assertEquals(JID('pubsub.example.org'), event.sender) + self.assertEquals('test', event.nodeIdentifier) + self.assertEquals(uri, event.redirectURI) + + d, self.protocol.deleteReceived = calledAsync(deleteReceived) + self.stub.send(message) + return d + + + def test_event_purge(self): + """ + Test receiving a purge event resulting in a call to purgeReceived. + """ + message = domish.Element((None, 'message')) + message['from'] = 'pubsub.example.org' + message['to'] = 'user@example.org/home' + event = message.addElement((NS_PUBSUB_EVENT, 'event')) + items = event.addElement('purge') + items['node'] = 'test' + + def purgeReceived(event): + self.assertEquals(JID('user@example.org/home'), event.recipient) + self.assertEquals(JID('pubsub.example.org'), event.sender) + self.assertEquals('test', event.nodeIdentifier) + + d, self.protocol.purgeReceived = calledAsync(purgeReceived) + self.stub.send(message) + return d + + + def test_createNode(self): + """ + Test sending create request. + """ + + def cb(nodeIdentifier): + self.assertEquals('test', nodeIdentifier) + + d = self.protocol.createNode(JID('pubsub.example.org'), 'test') + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'create', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_createNodeInstant(self): + """ + Test sending create request resulting in an instant node. + """ + + def cb(nodeIdentifier): + self.assertEquals('test', nodeIdentifier) + + d = self.protocol.createNode(JID('pubsub.example.org')) + d.addCallback(cb) + + iq = self.stub.output[-1] + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'create', NS_PUBSUB)) + child = children[0] + self.assertFalse(child.hasAttribute('node')) + + response = toResponse(iq, 'result') + command = response.addElement((NS_PUBSUB, 'pubsub')) + create = command.addElement('create') + create['node'] = 'test' + self.stub.send(response) + return d + + + def test_createNodeRenamed(self): + """ + Test sending create request resulting in renamed node. + """ + + def cb(nodeIdentifier): + self.assertEquals('test2', nodeIdentifier) + + d = self.protocol.createNode(JID('pubsub.example.org'), 'test') + d.addCallback(cb) + + iq = self.stub.output[-1] + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'create', NS_PUBSUB)) + child = children[0] + self.assertEquals('test', child['node']) + + response = toResponse(iq, 'result') + command = response.addElement((NS_PUBSUB, 'pubsub')) + create = command.addElement('create') + create['node'] = 'test2' + self.stub.send(response) + return d + + + def test_createNodeWithSender(self): + """ + Test sending create request from a specific JID. + """ + + d = self.protocol.createNode(JID('pubsub.example.org'), 'test', + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_createNodeWithConfig(self): + """ + Test sending create request with configuration options + """ + + options = { + 'pubsub#title': 'Princely Musings (Atom)', + 'pubsub#deliver_payloads': True, + 'pubsub#persist_items': '1', + 'pubsub#max_items': '10', + 'pubsub#access_model': 'open', + 'pubsub#type': 'http://www.w3.org/2005/Atom', + } + + d = self.protocol.createNode(JID('pubsub.example.org'), 'test', + sender=JID('user@example.org'), + options=options) + + iq = self.stub.output[-1] + + # check if there is exactly one configure element + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'configure', NS_PUBSUB)) + self.assertEqual(1, len(children)) + + # check that it has a configuration form + form = data_form.findForm(children[0], NS_PUBSUB_NODE_CONFIG) + self.assertEqual('submit', form.formType) + + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_deleteNode(self): + """ + Test sending delete request. + """ + + d = self.protocol.deleteNode(JID('pubsub.example.org'), 'test') + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB_OWNER, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'delete', NS_PUBSUB_OWNER)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_deleteNodeWithSender(self): + """ + Test sending delete request. + """ + + d = self.protocol.deleteNode(JID('pubsub.example.org'), 'test', + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_publish(self): + """ + Test sending publish request. + """ + + item = pubsub.Item() + d = self.protocol.publish(JID('pubsub.example.org'), 'test', [item]) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'publish', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + items = list(domish.generateElementsQNamed(child.children, + 'item', NS_PUBSUB)) + self.assertEquals(1, len(items)) + self.assertIdentical(item, items[0]) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_publishNoItems(self): + """ + Test sending publish request without items. + """ + + d = self.protocol.publish(JID('pubsub.example.org'), 'test') + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'publish', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_publishWithSender(self): + """ + Test sending publish request from a specific JID. + """ + + item = pubsub.Item() + d = self.protocol.publish(JID('pubsub.example.org'), 'test', [item], + JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + response = toResponse(iq, 'result') + self.stub.send(response) + return d + + + def test_subscribe(self): + """ + Test sending subscription request. + """ + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'subscribe', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + self.assertEquals('user@example.org', child['jid']) + + response = toResponse(iq, 'result') + pubsub = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = pubsub.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'subscribed' + self.stub.send(response) + return d + + + def test_subscribeReturnsSubscription(self): + """ + A successful subscription should return a Subscription instance. + """ + def cb(subscription): + self.assertEqual(JID('user@example.org'), subscription.subscriber) + + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + d.addCallback(cb) + + iq = self.stub.output[-1] + + response = toResponse(iq, 'result') + pubsub = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = pubsub.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'subscribed' + self.stub.send(response) + return d + + + def test_subscribePending(self): + """ + Test sending subscription request that results in a pending + subscription. + """ + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + + iq = self.stub.output[-1] + response = toResponse(iq, 'result') + command = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = command.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'pending' + self.stub.send(response) + self.assertFailure(d, pubsub.SubscriptionPending) + return d + + + def test_subscribeUnconfigured(self): + """ + Test sending subscription request that results in an unconfigured + subscription. + """ + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + + iq = self.stub.output[-1] + response = toResponse(iq, 'result') + command = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = command.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'unconfigured' + self.stub.send(response) + self.assertFailure(d, pubsub.SubscriptionUnconfigured) + return d + + + def test_subscribeWithOptions(self): + options = {'pubsub#deliver': False} + + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + options=options) + iq = self.stub.output[-1] + + # Check options present + childNames = [] + for element in iq.pubsub.elements(): + if element.uri == NS_PUBSUB: + childNames.append(element.name) + + self.assertEqual(['subscribe', 'options'], childNames) + form = data_form.findForm(iq.pubsub.options, + NS_PUBSUB_SUBSCRIBE_OPTIONS) + self.assertEqual('submit', form.formType) + form.typeCheck({'pubsub#deliver': {'type': 'boolean'}}) + self.assertEqual(options, form.getValues()) + + # Send response + response = toResponse(iq, 'result') + pubsub = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = pubsub.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'subscribed' + self.stub.send(response) + + return d + + + def test_subscribeWithSender(self): + """ + Test sending subscription request from a specific JID. + """ + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + response = toResponse(iq, 'result') + pubsub = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = pubsub.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'subscribed' + self.stub.send(response) + return d + + + def test_subscribeReturningSubscriptionIdentifier(self): + """ + Test sending subscription request with subscription identifier. + """ + def cb(subscription): + self.assertEqual('1234', subscription.subscriptionIdentifier) + + d = self.protocol.subscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + d.addCallback(cb) + + iq = self.stub.output[-1] + + response = toResponse(iq, 'result') + pubsub = response.addElement((NS_PUBSUB, 'pubsub')) + subscription = pubsub.addElement('subscription') + subscription['node'] = 'test' + subscription['jid'] = 'user@example.org' + subscription['subscription'] = 'subscribed' + subscription['subid'] = '1234' + self.stub.send(response) + return d + + + def test_unsubscribe(self): + """ + Test sending unsubscription request. + """ + d = self.protocol.unsubscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'unsubscribe', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + self.assertEquals('user@example.org', child['jid']) + + self.stub.send(toResponse(iq, 'result')) + return d + + + def test_unsubscribeWithSender(self): + """ + Test sending unsubscription request from a specific JID. + """ + d = self.protocol.unsubscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + self.stub.send(toResponse(iq, 'result')) + return d + + + def test_unsubscribeWithSubscriptionIdentifier(self): + """ + Test sending unsubscription request with subscription identifier. + """ + d = self.protocol.unsubscribe(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + subscriptionIdentifier='1234') + + iq = self.stub.output[-1] + child = iq.pubsub.unsubscribe + self.assertEquals('1234', child['subid']) + + self.stub.send(toResponse(iq, 'result')) + return d + + + def test_items(self): + """ + Test sending items request. + """ + def cb(items): + self.assertEquals([], items) + + d = self.protocol.items(JID('pubsub.example.org'), 'test') + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + response = toResponse(iq, 'result') + items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items') + items['node'] = 'test' + + self.stub.send(response) + + return d + + + def test_itemsMaxItems(self): + """ + Test sending items request, with limit on the number of items. + """ + def cb(items): + self.assertEquals(2, len(items)) + self.assertEquals([item1, item2], items) + + d = self.protocol.items(JID('pubsub.example.org'), 'test', maxItems=2) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + self.assertEquals('2', child['max_items']) + + response = toResponse(iq, 'result') + items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item1' + item2 = items.addElement('item') + item2['id'] = 'item2' + + self.stub.send(response) + + return d + + + def test_itemsWithItemIdentifiers(self): + """ + Test sending items request with item identifiers. + """ + def cb(items): + self.assertEquals(2, len(items)) + self.assertEquals([item1, item2], items) + + d = self.protocol.items(JID('pubsub.example.org'), 'test', + itemIdentifiers=['item1', 'item2']) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + itemIdentifiers = [item.getAttribute('id') for item in + domish.generateElementsQNamed(child.children, 'item', + NS_PUBSUB)] + self.assertEquals(['item1', 'item2'], itemIdentifiers) + + response = toResponse(iq, 'result') + items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item1' + item2 = items.addElement('item') + item2['id'] = 'item2' + + self.stub.send(response) + + return d + + + def test_itemsWithSubscriptionIdentifier(self): + """ + Test sending items request with a subscription identifier. + """ + + d = self.protocol.items(JID('pubsub.example.org'), 'test', + subscriptionIdentifier='1234') + + iq = self.stub.output[-1] + child = iq.pubsub.items + self.assertEquals('1234', child['subid']) + + response = toResponse(iq, 'result') + items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items') + items['node'] = 'test' + + self.stub.send(response) + return d + + + def test_itemsWithSender(self): + """ + Test sending items request from a specific JID. + """ + + d = self.protocol.items(JID('pubsub.example.org'), 'test', + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + response = toResponse(iq, 'result') + items = response.addElement((NS_PUBSUB, 'pubsub')).addElement('items') + items['node'] = 'test' + + self.stub.send(response) + return d + + + def test_retractItems(self): + """ + Test sending items retraction. + """ + d = self.protocol.retractItems(JID('pubsub.example.org'), 'test', + itemIdentifiers=['item1', 'item2']) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('set', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'retract', NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + itemIdentifiers = [item.getAttribute('id') for item in + domish.generateElementsQNamed(child.children, 'item', + NS_PUBSUB)] + self.assertEquals(['item1', 'item2'], itemIdentifiers) + + self.stub.send(toResponse(iq, 'result')) + return d + + + def test_retractItemsWithSender(self): + """ + Test retracting items request from a specific JID. + """ + d = self.protocol.retractItems(JID('pubsub.example.org'), 'test', + itemIdentifiers=['item1', 'item2'], + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEquals('user@example.org', iq['from']) + + self.stub.send(toResponse(iq, 'result')) + return d + + + def test_getOptions(self): + def cb(form): + self.assertEqual('form', form.formType) + self.assertEqual(NS_PUBSUB_SUBSCRIBE_OPTIONS, form.formNamespace) + field = form.fields['pubsub#deliver'] + self.assertEqual('boolean', field.fieldType) + self.assertIdentical(True, field.value) + self.assertEqual('Enable delivery?', field.label) + + d = self.protocol.getOptions(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + sender=JID('user@example.org')) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEqual('pubsub.example.org', iq.getAttribute('to')) + self.assertEqual('get', iq.getAttribute('type')) + self.assertEqual('pubsub', iq.pubsub.name) + self.assertEqual(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'options', NS_PUBSUB)) + self.assertEqual(1, len(children)) + child = children[0] + self.assertEqual('test', child['node']) + + self.assertEqual(0, len(child.children)) + + # Send response + form = data_form.Form('form', formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS) + form.addField(data_form.Field('boolean', var='pubsub#deliver', + label='Enable delivery?', + value=True)) + response = toResponse(iq, 'result') + response.addElement((NS_PUBSUB, 'pubsub')) + response.pubsub.addElement('options') + response.pubsub.options.addChild(form.toElement()) + self.stub.send(response) + + return d + + + def test_getOptionsWithSubscriptionIdentifier(self): + """ + Getting options with a subid should have the subid in the request. + """ + + d = self.protocol.getOptions(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + sender=JID('user@example.org'), + subscriptionIdentifier='1234') + + iq = self.stub.output[-1] + child = iq.pubsub.options + self.assertEqual('1234', child['subid']) + + # Send response + form = data_form.Form('form', formNamespace=NS_PUBSUB_SUBSCRIBE_OPTIONS) + form.addField(data_form.Field('boolean', var='pubsub#deliver', + label='Enable delivery?', + value=True)) + response = toResponse(iq, 'result') + response.addElement((NS_PUBSUB, 'pubsub')) + response.pubsub.addElement('options') + response.pubsub.options.addChild(form.toElement()) + self.stub.send(response) + + return d + + + def test_setOptions(self): + """ + setOptions should send out a options-set request. + """ + options = {'pubsub#deliver': False} + + d = self.protocol.setOptions(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + options, + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + self.assertEqual('pubsub.example.org', iq.getAttribute('to')) + self.assertEqual('set', iq.getAttribute('type')) + self.assertEqual('pubsub', iq.pubsub.name) + self.assertEqual(NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'options', NS_PUBSUB)) + self.assertEqual(1, len(children)) + child = children[0] + self.assertEqual('test', child['node']) + + form = data_form.findForm(child, NS_PUBSUB_SUBSCRIBE_OPTIONS) + self.assertEqual('submit', form.formType) + form.typeCheck({'pubsub#deliver': {'type': 'boolean'}}) + self.assertEqual(options, form.getValues()) + + response = toResponse(iq, 'result') + self.stub.send(response) + + return d + + + def test_setOptionsWithSubscriptionIdentifier(self): + """ + setOptions should send out a options-set request with subid. + """ + options = {'pubsub#deliver': False} + + d = self.protocol.setOptions(JID('pubsub.example.org'), 'test', + JID('user@example.org'), + options, + subscriptionIdentifier='1234', + sender=JID('user@example.org')) + + iq = self.stub.output[-1] + child = iq.pubsub.options + self.assertEqual('1234', child['subid']) + + form = data_form.findForm(child, NS_PUBSUB_SUBSCRIBE_OPTIONS) + self.assertEqual('submit', form.formType) + form.typeCheck({'pubsub#deliver': {'type': 'boolean'}}) + self.assertEqual(options, form.getValues()) + + response = toResponse(iq, 'result') + self.stub.send(response) + + return d + + +class PubSubRequestTest(unittest.TestCase): + + def test_fromElementUnknown(self): + """ + An unknown verb raises NotImplementedError. + """ + + xml = """ + + + + + + """ + + self.assertRaises(NotImplementedError, + pubsub.PubSubRequest.fromElement, parseXml(xml)) + + + def test_fromElementKnownBadCombination(self): + """ + Multiple verbs in an unknown configuration raises NotImplementedError. + """ + + xml = """ + + + + + + + """ + + self.assertRaises(NotImplementedError, + pubsub.PubSubRequest.fromElement, parseXml(xml)) + + def test_fromElementPublish(self): + """ + Test parsing a publish request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('publish', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual([], request.items) + + + def test_fromElementPublishItems(self): + """ + Test parsing a publish request with items. + """ + + xml = """ + + + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual(2, len(request.items)) + self.assertEqual(u'item1', request.items[0]["id"]) + self.assertEqual(u'item2', request.items[1]["id"]) + + + def test_fromElementPublishItemsOptions(self): + """ + Test parsing a publish request with items and options. + + Note that publishing options are not supported, but passing them + shouldn't affect processing of the publish request itself. + """ + + xml = """ + + + + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual(2, len(request.items)) + self.assertEqual(u'item1', request.items[0]["id"]) + self.assertEqual(u'item2', request.items[1]["id"]) + + def test_fromElementPublishNoNode(self): + """ + A publish request to the root node should raise an exception. + """ + xml = """ + + + + + + """ + + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual(NS_PUBSUB_ERRORS, err.appCondition.uri) + self.assertEqual('nodeid-required', err.appCondition.name) + + + def test_fromElementSubscribe(self): + """ + Test parsing a subscription request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('subscribe', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual(JID('user@example.org/Home'), request.subscriber) + + + def test_fromElementSubscribeEmptyNode(self): + """ + Test parsing a subscription request to the root node. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('', request.nodeIdentifier) + + + def test_fromElementSubscribeNoJID(self): + """ + Subscribe requests without a JID should raise a bad-request exception. + """ + xml = """ + + + + + + """ + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual(NS_PUBSUB_ERRORS, err.appCondition.uri) + self.assertEqual('jid-required', err.appCondition.name) + + + def test_fromElementSubscribeWithOptions(self): + """ + Test parsing a subscription request. + """ + + xml = """ + + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + + 1 + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('subscribe', request.verb) + request.options.typeCheck({'pubsub#deliver': {'type': 'boolean'}}) + self.assertEqual({'pubsub#deliver': True}, request.options.getValues()) + + + def test_fromElementSubscribeWithOptionsBadFormType(self): + """ + The options form should have the right type. + """ + + xml = """ + + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + + 1 + + + + + + """ + + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual("Unexpected form type 'result'", err.text) + self.assertEqual(None, err.appCondition) + + + def test_fromElementSubscribeWithOptionsEmpty(self): + """ + When no (suitable) form is found, the options are empty. + """ + + xml = """ + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('subscribe', request.verb) + self.assertEqual({}, request.options.getValues()) + + + def test_fromElementUnsubscribe(self): + """ + Test parsing an unsubscription request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('unsubscribe', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual(JID('user@example.org/Home'), request.subscriber) + + + def test_fromElementUnsubscribeWithSubscriptionIdentifier(self): + """ + Test parsing an unsubscription request with subscription identifier. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('1234', request.subscriptionIdentifier) + + + def test_fromElementUnsubscribeNoJID(self): + """ + Unsubscribe requests without a JID should raise a bad-request exception. + """ + xml = """ + + + + + + """ + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual(NS_PUBSUB_ERRORS, err.appCondition.uri) + self.assertEqual('jid-required', err.appCondition.name) + + + def test_fromElementOptionsGet(self): + """ + Test parsing a request for getting subscription options. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('optionsGet', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual(JID('user@example.org/Home'), request.subscriber) + + + def test_fromElementOptionsGetWithSubscriptionIdentifier(self): + """ + Test parsing a request for getting subscription options with subid. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('1234', request.subscriptionIdentifier) + + + def test_fromElementOptionsSet(self): + """ + Test parsing a request for setting subscription options. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + 1 + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('optionsSet', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual(JID('user@example.org/Home'), request.subscriber) + self.assertEqual({'pubsub#deliver': '1'}, request.options.getValues()) + + + def test_fromElementOptionsSetWithSubscriptionIdentifier(self): + """ + Test parsing a request for setting subscription options with subid. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + 1 + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('1234', request.subscriptionIdentifier) + + + def test_fromElementOptionsSetCancel(self): + """ + Test parsing a request for cancelling setting subscription options. + """ + + xml = """ + + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('cancel', request.options.formType) + + + def test_fromElementOptionsSetBadFormType(self): + """ + On a options set request unknown fields should be ignored. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + 1 + + + + + """ + + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual("Unexpected form type 'result'", err.text) + self.assertEqual(None, err.appCondition) + + + def test_fromElementOptionsSetNoForm(self): + """ + On a options set request a form is required. + """ + + xml = """ + + + + + + """ + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual(None, err.appCondition) + + + def test_fromElementSubscriptions(self): + """ + Test parsing a request for all subscriptions. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('subscriptions', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + + + def test_fromElementAffiliations(self): + """ + Test parsing a request for all affiliations. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('affiliations', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + + + def test_fromElementCreate(self): + """ + Test parsing a request to create a node. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('create', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('mynode', request.nodeIdentifier) + self.assertIdentical(None, request.options) + + + def test_fromElementCreateInstant(self): + """ + Test parsing a request to create an instant node. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertIdentical(None, request.nodeIdentifier) + + + def test_fromElementCreateConfigureEmpty(self): + """ + Test parsing a request to create a node with an empty configuration. + """ + + xml = """ + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual({}, request.options.getValues()) + self.assertEqual(u'mynode', request.nodeIdentifier) + + + def test_fromElementCreateConfigureEmptyWrongOrder(self): + """ + Test parsing a request to create a node and configure, wrong order. + + The C{configure} element should come after the C{create} request, + but we should accept both orders. + """ + + xml = """ + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual({}, request.options.getValues()) + self.assertEqual(u'mynode', request.nodeIdentifier) + + + def test_fromElementCreateConfigure(self): + """ + Test parsing a request to create a node. + """ + + xml = """ + + + + + + + http://jabber.org/protocol/pubsub#node_config + + open + 0 + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + values = request.options + self.assertIn('pubsub#access_model', values) + self.assertEqual(u'open', values['pubsub#access_model']) + self.assertIn('pubsub#persist_items', values) + self.assertEqual(u'0', values['pubsub#persist_items']) + + + def test_fromElementCreateConfigureBadFormType(self): + """ + The form of a node creation request should have the right type. + """ + + xml = """ + + + + + + + http://jabber.org/protocol/pubsub#node_config + + open + 0 + + + + + """ + + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual("Unexpected form type 'result'", err.text) + self.assertEqual(None, err.appCondition) + + + def test_fromElementDefault(self): + """ + Parsing default node configuration request sets required attributes. + + Besides C{verb}, C{sender} and C{recipient}, we expect C{nodeType} + to be set. If not passed it receives the default C{u'leaf'}. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEquals(u'default', request.verb) + self.assertEquals(JID('user@example.org'), request.sender) + self.assertEquals(JID('pubsub.example.org'), request.recipient) + self.assertEquals(u'leaf', request.nodeType) + + + def test_fromElementDefaultCollection(self): + """ + Parsing default request for collection sets nodeType to collection. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + + collection + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEquals('collection', request.nodeType) + + + def test_fromElementConfigureGet(self): + """ + Test parsing a node configuration get request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('configureGet', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + + + def test_fromElementConfigureSet(self): + """ + On a node configuration set request the Data Form is parsed. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('configureSet', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual({'pubsub#deliver_payloads': '0', + 'pubsub#persist_items': '1'}, + request.options.getValues()) + + + def test_fromElementConfigureSetCancel(self): + """ + The node configuration is cancelled, so no options. + """ + + xml = """ + + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('cancel', request.options.formType) + + + def test_fromElementConfigureSetBadFormType(self): + """ + The form of a node configuraton set request should have the right type. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual("Unexpected form type 'result'", err.text) + self.assertEqual(None, err.appCondition) + + + def test_fromElementConfigureSetNoForm(self): + """ + On a node configuration set request a form is required. + """ + + xml = """ + + + + + + """ + err = self.assertRaises(error.StanzaError, + pubsub.PubSubRequest.fromElement, + parseXml(xml)) + self.assertEqual('bad-request', err.condition) + self.assertEqual(None, err.appCondition) + + + def test_fromElementItems(self): + """ + Test parsing an items request. + """ + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('items', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertIdentical(None, request.maxItems) + self.assertIdentical(None, request.subscriptionIdentifier) + self.assertEqual([], request.itemIdentifiers) + + + def test_fromElementItemsSubscriptionIdentifier(self): + """ + Test parsing an items request with subscription identifier. + """ + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('1234', request.subscriptionIdentifier) + + + def test_fromElementRetract(self): + """ + Test parsing a retract request. + """ + + xml = """ + + + + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('retract', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + self.assertEqual(['item1', 'item2'], request.itemIdentifiers) + + + def test_fromElementPurge(self): + """ + Test parsing a purge request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('purge', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + + + def test_fromElementDelete(self): + """ + Test parsing a delete request. + """ + + xml = """ + + + + + + """ + + request = pubsub.PubSubRequest.fromElement(parseXml(xml)) + self.assertEqual('delete', request.verb) + self.assertEqual(JID('user@example.org'), request.sender) + self.assertEqual(JID('pubsub.example.org'), request.recipient) + self.assertEqual('test', request.nodeIdentifier) + + + +class PubSubServiceTest(unittest.TestCase, TestableRequestHandlerMixin): + """ + Tests for L{pubsub.PubSubService}. + """ + + def setUp(self): + self.stub = XmlStreamStub() + self.resource = pubsub.PubSubResource() + self.service = pubsub.PubSubService(self.resource) + self.service.send = self.stub.xmlstream.send + + def test_interface(self): + """ + Do instances of L{pubsub.PubSubService} provide L{iwokkel.IPubSubService}? + """ + verify.verifyObject(iwokkel.IPubSubService, self.service) + + + def test_interfaceIDisco(self): + """ + Do instances of L{pubsub.PubSubService} provide L{iwokkel.IDisco}? + """ + verify.verifyObject(iwokkel.IDisco, self.service) + + + def test_connectionMade(self): + """ + Verify setup of observers in L{pubsub.connectionMade}. + """ + requests = [] + + def handleRequest(iq): + requests.append(iq) + + self.service.xmlstream = self.stub.xmlstream + self.service.handleRequest = handleRequest + self.service.connectionMade() + + for namespace in (NS_PUBSUB, NS_PUBSUB_OWNER): + for stanzaType in ('get', 'set'): + iq = domish.Element((None, 'iq')) + iq['type'] = stanzaType + iq.addElement((namespace, 'pubsub')) + self.stub.xmlstream.dispatch(iq) + + self.assertEqual(4, len(requests)) + + + def test_getDiscoInfo(self): + """ + Test getDiscoInfo calls getNodeInfo and returns some minimal info. + """ + def cb(info): + discoInfo = disco.DiscoInfo() + for item in info: + discoInfo.append(item) + self.assertIn(('pubsub', 'service'), discoInfo.identities) + self.assertIn(disco.NS_DISCO_ITEMS, discoInfo.features) + + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), '') + d.addCallback(cb) + return d + + + def test_getDiscoInfoNodeType(self): + """ + Test getDiscoInfo with node type. + """ + def cb(info): + discoInfo = disco.DiscoInfo() + for item in info: + discoInfo.append(item) + self.assertIn(('pubsub', 'collection'), discoInfo.identities) + + def getInfo(requestor, target, nodeIdentifier): + return defer.succeed({'type': 'collection', + 'meta-data': {}}) + + self.resource.getInfo = getInfo + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), '') + d.addCallback(cb) + return d + + + def test_getDiscoInfoMetaData(self): + """ + Test getDiscoInfo with returned meta data. + """ + def cb(info): + discoInfo = disco.DiscoInfo() + for item in info: + discoInfo.append(item) + + self.assertIn(('pubsub', 'leaf'), discoInfo.identities) + self.assertIn(NS_PUBSUB_META_DATA, discoInfo.extensions) + form = discoInfo.extensions[NS_PUBSUB_META_DATA] + self.assertIn('pubsub#node_type', form.fields) + + def getInfo(requestor, target, nodeIdentifier): + metaData = [{'var': 'pubsub#persist_items', + 'label': 'Persist items to storage', + 'value': True}] + return defer.succeed({'type': 'leaf', 'meta-data': metaData}) + + self.resource.getInfo = getInfo + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), '') + d.addCallback(cb) + return d + + + def test_getDiscoInfoResourceFeatures(self): + """ + Test getDiscoInfo with the resource features. + """ + def cb(info): + discoInfo = disco.DiscoInfo() + for item in info: + discoInfo.append(item) + self.assertIn('http://jabber.org/protocol/pubsub#publish', + discoInfo.features) + + self.resource.features = ['publish'] + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), '') + d.addCallback(cb) + return d + + + def test_getDiscoInfoBadResponse(self): + """ + If getInfo returns invalid response, it should be logged, then ignored. + """ + def cb(info): + self.assertEquals([], info) + self.assertEqual(1, len(self.flushLoggedErrors(TypeError))) + + def getInfo(requestor, target, nodeIdentifier): + return defer.succeed('bad response') + + self.resource.getInfo = getInfo + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), 'test') + d.addCallback(cb) + return d + + + def test_getDiscoInfoException(self): + """ + If getInfo returns invalid response, it should be logged, then ignored. + """ + def cb(info): + self.assertEquals([], info) + self.assertEqual(1, len(self.flushLoggedErrors(NotImplementedError))) + + def getInfo(requestor, target, nodeIdentifier): + return defer.fail(NotImplementedError()) + + self.resource.getInfo = getInfo + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), 'test') + d.addCallback(cb) + return d + + + def test_getDiscoItemsRoot(self): + """ + Test getDiscoItems on the root node. + """ + def getNodes(requestor, service, nodeIdentifier): + return defer.succeed(['node1', 'node2']) + + def cb(items): + self.assertEqual(2, len(items)) + item1, item2 = items + + self.assertEqual(JID('pubsub.example.org'), item1.entity) + self.assertEqual('node1', item1.nodeIdentifier) + + self.assertEqual(JID('pubsub.example.org'), item2.entity) + self.assertEqual('node2', item2.nodeIdentifier) + + self.resource.getNodes = getNodes + d = self.service.getDiscoItems(JID('user@example.org/home'), + JID('pubsub.example.org'), + '') + d.addCallback(cb) + return d + + + def test_getDiscoItemsRootHideNodes(self): + """ + Test getDiscoItems on the root node. + """ + def getNodes(requestor, service, nodeIdentifier): + raise Exception("Unexpected call to getNodes") + + def cb(items): + self.assertEqual([], items) + + self.service.hideNodes = True + self.resource.getNodes = getNodes + d = self.service.getDiscoItems(JID('user@example.org/home'), + JID('pubsub.example.org'), + '') + d.addCallback(cb) + return d + + + def test_getDiscoItemsNonRoot(self): + """ + Test getDiscoItems on a non-root node. + """ + def getNodes(requestor, service, nodeIdentifier): + return defer.succeed(['node1', 'node2']) + + def cb(items): + self.assertEqual(2, len(items)) + + self.resource.getNodes = getNodes + d = self.service.getDiscoItems(JID('user@example.org/home'), + JID('pubsub.example.org'), + 'test') + d.addCallback(cb) + return d + + + def test_on_publish(self): + """ + A publish request should result in L{PubSubService.publish} being + called. + """ + + xml = """ + + + + + + """ + + def publish(request): + return defer.succeed(None) + + self.resource.publish = publish + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_subscribe(self): + """ + A successful subscription should return the current subscription. + """ + + xml = """ + + + + + + """ + + def subscribe(request): + return defer.succeed(pubsub.Subscription(request.nodeIdentifier, + request.subscriber, + 'subscribed')) + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB, element.uri) + subscription = element.subscription + self.assertEqual(NS_PUBSUB, subscription.uri) + self.assertEqual('test', subscription['node']) + self.assertEqual('user@example.org/Home', subscription['jid']) + self.assertEqual('subscribed', subscription['subscription']) + + self.resource.subscribe = subscribe + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_subscribeEmptyNode(self): + """ + A successful subscription on root node should return no node attribute. + """ + + xml = """ + + + + + + """ + + def subscribe(request): + return defer.succeed(pubsub.Subscription(request.nodeIdentifier, + request.subscriber, + 'subscribed')) + + def cb(element): + self.assertFalse(element.subscription.hasAttribute('node')) + + self.resource.subscribe = subscribe + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_subscribeSubscriptionIdentifier(self): + """ + If a subscription returns a subid, this should be available. + """ + + xml = """ + + + + + + """ + + def subscribe(request): + subscription = pubsub.Subscription(request.nodeIdentifier, + request.subscriber, + 'subscribed', + subscriptionIdentifier='1234') + return defer.succeed(subscription) + + def cb(element): + self.assertEqual('1234', element.subscription.getAttribute('subid')) + + self.resource.subscribe = subscribe + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_unsubscribe(self): + """ + A successful unsubscription should return an empty response. + """ + + xml = """ + + + + + + """ + + def unsubscribe(request): + return defer.succeed(None) + + def cb(element): + self.assertIdentical(None, element) + + self.resource.unsubscribe = unsubscribe + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_unsubscribeSubscriptionIdentifier(self): + """ + A successful unsubscription with subid should return an empty response. + """ + + xml = """ + + + + + + """ + + def unsubscribe(request): + self.assertEqual('1234', request.subscriptionIdentifier) + return defer.succeed(None) + + def cb(element): + self.assertIdentical(None, element) + + self.resource.unsubscribe = unsubscribe + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_optionsGet(self): + """ + Getting subscription options is not supported. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_optionsSet(self): + """ + Setting subscription options is not supported. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#subscribe_options + + 1 + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_subscriptions(self): + """ + A subscriptions request should result in + L{PubSubService.subscriptions} being called and the result prepared + for the response. + """ + + xml = """ + + + + + + """ + + def subscriptions(request): + subscription = pubsub.Subscription('test', JID('user@example.org'), + 'subscribed') + return defer.succeed([subscription]) + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB, element.uri) + self.assertEqual(NS_PUBSUB, element.subscriptions.uri) + children = list(element.subscriptions.elements()) + self.assertEqual(1, len(children)) + subscription = children[0] + self.assertEqual('subscription', subscription.name) + self.assertEqual(NS_PUBSUB, subscription.uri, NS_PUBSUB) + self.assertEqual('user@example.org', subscription['jid']) + self.assertEqual('test', subscription['node']) + self.assertEqual('subscribed', subscription['subscription']) + + self.resource.subscriptions = subscriptions + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_subscriptionsWithSubscriptionIdentifier(self): + """ + A subscriptions request response should include subids, if set. + """ + + xml = """ + + + + + + """ + + def subscriptions(request): + subscription = pubsub.Subscription('test', JID('user@example.org'), + 'subscribed', + subscriptionIdentifier='1234') + return defer.succeed([subscription]) + + def cb(element): + subscription = element.subscriptions.subscription + self.assertEqual('1234', subscription['subid']) + + self.resource.subscriptions = subscriptions + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_affiliations(self): + """ + A subscriptions request should result in + L{PubSubService.affiliations} being called and the result prepared + for the response. + """ + + xml = """ + + + + + + """ + + def affiliations(request): + affiliation = ('test', 'owner') + return defer.succeed([affiliation]) + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB, element.uri) + self.assertEqual(NS_PUBSUB, element.affiliations.uri) + children = list(element.affiliations.elements()) + self.assertEqual(1, len(children)) + affiliation = children[0] + self.assertEqual('affiliation', affiliation.name) + self.assertEqual(NS_PUBSUB, affiliation.uri) + self.assertEqual('test', affiliation['node']) + self.assertEqual('owner', affiliation['affiliation']) + + self.resource.affiliations = affiliations + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_create(self): + """ + Replies to create node requests don't return the created node. + """ + + xml = """ + + + + + + """ + + def create(request): + return defer.succeed(request.nodeIdentifier) + + def cb(element): + self.assertIdentical(None, element) + + self.resource.create = create + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_createChanged(self): + """ + Replies to create node requests return the created node if changed. + """ + + xml = """ + + + + + + """ + + def create(request): + return defer.succeed(u'myrenamednode') + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB, element.uri) + self.assertEqual(NS_PUBSUB, element.create.uri) + self.assertEqual(u'myrenamednode', + element.create.getAttribute('node')) + + self.resource.create = create + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_createInstant(self): + """ + Replies to create instant node requests return the created node. + """ + + xml = """ + + + + + + """ + + def create(request): + return defer.succeed(u'random') + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB, element.uri) + self.assertEqual(NS_PUBSUB, element.create.uri) + self.assertEqual(u'random', element.create.getAttribute('node')) + + self.resource.create = create + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_createWithConfig(self): + """ + On a node create with configuration request the Data Form is parsed and + L{PubSubResource.create} is called with the passed options. + """ + + xml = """ + + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def getConfigurationOptions(): + return { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"} + } + + def create(request): + self.assertEqual({'pubsub#deliver_payloads': False, + 'pubsub#persist_items': True}, + request.options.getValues()) + return defer.succeed(None) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.create = create + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_default(self): + """ + A default request returns default options filtered by available fields. + """ + + xml = """ + + + + + + """ + fieldDefs = { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"} + } + + def getConfigurationOptions(): + return fieldDefs + + def default(request): + return defer.succeed({'pubsub#persist_items': 'false', + 'x-myfield': '1'}) + + def cb(element): + self.assertEquals('pubsub', element.name) + self.assertEquals(NS_PUBSUB_OWNER, element.uri) + self.assertEquals(NS_PUBSUB_OWNER, element.default.uri) + form = data_form.Form.fromElement(element.default.x) + self.assertEquals(NS_PUBSUB_NODE_CONFIG, form.formNamespace) + form.typeCheck(fieldDefs) + self.assertIn('pubsub#persist_items', form.fields) + self.assertFalse(form.fields['pubsub#persist_items'].value) + self.assertNotIn('x-myfield', form.fields) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.default = default + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_defaultUnknownNodeType(self): + """ + Unknown node types yield non-acceptable. + + Both C{getConfigurationOptions} and C{default} must not be called. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + + unknown + + + + + + + """ + + def getConfigurationOptions(): + self.fail("Unexpected call to getConfigurationOptions") + + def default(request): + self.fail("Unexpected call to default") + + def cb(result): + self.assertEquals('not-acceptable', result.condition) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.default = default + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_configureGet(self): + """ + On a node configuration get + requestL{PubSubResource.configureGet} is called and results in a + data form with the configuration. + """ + + xml = """ + + + + + + """ + + def getConfigurationOptions(): + return { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"}, + "pubsub#owner": + {"type": "jid-single", + "label": "Owner of the node"} + } + + def configureGet(request): + return defer.succeed({'pubsub#deliver_payloads': '0', + 'pubsub#persist_items': '1', + 'pubsub#owner': JID('user@example.org'), + 'x-myfield': 'a'}) + + def cb(element): + self.assertEqual('pubsub', element.name) + self.assertEqual(NS_PUBSUB_OWNER, element.uri) + self.assertEqual(NS_PUBSUB_OWNER, element.configure.uri) + form = data_form.Form.fromElement(element.configure.x) + self.assertEqual(NS_PUBSUB_NODE_CONFIG, form.formNamespace) + fields = form.fields + + self.assertIn('pubsub#deliver_payloads', fields) + field = fields['pubsub#deliver_payloads'] + self.assertEqual('boolean', field.fieldType) + field.typeCheck() + self.assertEqual(False, field.value) + + self.assertIn('pubsub#persist_items', fields) + field = fields['pubsub#persist_items'] + self.assertEqual('boolean', field.fieldType) + field.typeCheck() + self.assertEqual(True, field.value) + + self.assertIn('pubsub#owner', fields) + field = fields['pubsub#owner'] + self.assertEqual('jid-single', field.fieldType) + field.typeCheck() + self.assertEqual(JID('user@example.org'), field.value) + + self.assertNotIn('x-myfield', fields) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.configureGet = configureGet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_configureSet(self): + """ + On a node configuration set request the Data Form is parsed and + L{PubSubResource.configureSet} is called with the passed options. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def getConfigurationOptions(): + return { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"} + } + + def configureSet(request): + self.assertEqual({'pubsub#deliver_payloads': False, + 'pubsub#persist_items': True}, + request.options.getValues()) + return defer.succeed(None) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.configureSet = configureSet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_configureSetCancel(self): + """ + The node configuration is cancelled, + L{PubSubResource.configureSet} not called. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + + + + + """ + + def configureSet(request): + self.fail("Unexpected call to setConfiguration") + + self.resource.configureSet = configureSet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_configureSetIgnoreUnknown(self): + """ + On a node configuration set request unknown fields should be ignored. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def getConfigurationOptions(): + return { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"} + } + + def configureSet(request): + self.assertEquals(['pubsub#deliver_payloads'], + request.options.keys()) + + self.resource.getConfigurationOptions = getConfigurationOptions + self.resource.configureSet = configureSet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_configureSetBadFormType(self): + """ + On a node configuration set request unknown fields should be ignored. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def cb(result): + self.assertEquals('bad-request', result.condition) + self.assertEqual("Unexpected form type 'result'", result.text) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_items(self): + """ + On a items request, return all items for the given node. + """ + xml = """ + + + + + + """ + + def items(request): + return defer.succeed([pubsub.Item('current')]) + + def cb(element): + self.assertEqual(NS_PUBSUB, element.uri) + self.assertEqual(NS_PUBSUB, element.items.uri) + self.assertEqual(1, len(element.items.children)) + item = element.items.children[-1] + self.assertTrue(domish.IElement.providedBy(item)) + self.assertEqual('item', item.name) + self.assertEqual(NS_PUBSUB, item.uri) + self.assertEqual('current', item['id']) + + self.resource.items = items + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_retract(self): + """ + A retract request should result in L{PubSubResource.retract} + being called. + """ + + xml = """ + + + + + + + + + """ + + def retract(request): + return defer.succeed(None) + + self.resource.retract = retract + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_purge(self): + """ + A purge request should result in L{PubSubResource.purge} being + called. + """ + + xml = """ + + + + + + """ + + def purge(request): + return defer.succeed(None) + + self.resource.purge = purge + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_on_delete(self): + """ + A delete request should result in L{PubSubResource.delete} being + called. + """ + + xml = """ + + + + + + """ + + def delete(request): + return defer.succeed(None) + + self.resource.delete = delete + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + return self.handleRequest(xml) + + + def test_notifyPublish(self): + """ + Publish notifications are sent to the subscribers. + """ + subscriber = JID('user@example.org') + subscriptions = [pubsub.Subscription('test', subscriber, 'subscribed')] + items = [pubsub.Item('current')] + notifications = [(subscriber, subscriptions, items)] + self.service.notifyPublish(JID('pubsub.example.org'), 'test', + notifications) + message = self.stub.output[-1] + + self.assertEquals('message', message.name) + self.assertIdentical(None, message.uri) + self.assertEquals('user@example.org', message['to']) + self.assertEquals('pubsub.example.org', message['from']) + self.assertTrue(message.event) + self.assertEquals(NS_PUBSUB_EVENT, message.event.uri) + self.assertTrue(message.event.items) + self.assertEquals(NS_PUBSUB_EVENT, message.event.items.uri) + self.assertTrue(message.event.items.hasAttribute('node')) + self.assertEquals('test', message.event.items['node']) + itemElements = list(domish.generateElementsQNamed( + message.event.items.children, 'item', NS_PUBSUB_EVENT)) + self.assertEquals(1, len(itemElements)) + self.assertEquals('current', itemElements[0].getAttribute('id')) + + + def test_notifyPublishCollection(self): + """ + Publish notifications are sent to the subscribers of collections. + + The node the item was published to is on the C{items} element, while + the subscribed-to node is in the C{'Collections'} SHIM header. + """ + subscriber = JID('user@example.org') + subscriptions = [pubsub.Subscription('', subscriber, 'subscribed')] + items = [pubsub.Item('current')] + notifications = [(subscriber, subscriptions, items)] + self.service.notifyPublish(JID('pubsub.example.org'), 'test', + notifications) + message = self.stub.output[-1] + + self.assertTrue(message.event.items.hasAttribute('node')) + self.assertEquals('test', message.event.items['node']) + headers = shim.extractHeaders(message) + self.assertIn('Collection', headers) + self.assertIn('', headers['Collection']) + + + def test_notifyDelete(self): + """ + Subscribers should be sent a delete notification. + """ + subscriptions = [JID('user@example.org')] + self.service.notifyDelete(JID('pubsub.example.org'), 'test', + subscriptions) + message = self.stub.output[-1] + + self.assertEquals('message', message.name) + self.assertIdentical(None, message.uri) + self.assertEquals('user@example.org', message['to']) + self.assertEquals('pubsub.example.org', message['from']) + self.assertTrue(message.event) + self.assertEqual(NS_PUBSUB_EVENT, message.event.uri) + self.assertTrue(message.event.delete) + self.assertEqual(NS_PUBSUB_EVENT, message.event.delete.uri) + self.assertTrue(message.event.delete.hasAttribute('node')) + self.assertEqual('test', message.event.delete['node']) + + + def test_notifyDeleteRedirect(self): + """ + Subscribers should be sent a delete notification with redirect. + """ + redirectURI = 'xmpp:pubsub.example.org?;node=test2' + subscriptions = [JID('user@example.org')] + self.service.notifyDelete(JID('pubsub.example.org'), 'test', + subscriptions, redirectURI) + message = self.stub.output[-1] + + self.assertEquals('message', message.name) + self.assertIdentical(None, message.uri) + self.assertEquals('user@example.org', message['to']) + self.assertEquals('pubsub.example.org', message['from']) + self.assertTrue(message.event) + self.assertEqual(NS_PUBSUB_EVENT, message.event.uri) + self.assertTrue(message.event.delete) + self.assertEqual(NS_PUBSUB_EVENT, message.event.delete.uri) + self.assertTrue(message.event.delete.hasAttribute('node')) + self.assertEqual('test', message.event.delete['node']) + self.assertTrue(message.event.delete.redirect) + self.assertEqual(NS_PUBSUB_EVENT, message.event.delete.redirect.uri) + self.assertTrue(message.event.delete.redirect.hasAttribute('uri')) + self.assertEqual(redirectURI, message.event.delete.redirect['uri']) + + + def test_on_subscriptionsGet(self): + """ + Getting subscription options is not supported. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('manage-subscriptions', + result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_subscriptionsSet(self): + """ + Setting subscription options is not supported. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('manage-subscriptions', + result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_affiliationsGet(self): + """ + Getting node affiliations should have. + """ + + xml = """ + + + + + + """ + + def affiliationsGet(request): + self.assertEquals('test', request.nodeIdentifier) + return defer.succeed({JID('user@example.org'): 'owner'}) + + def cb(element): + self.assertEquals(u'pubsub', element.name) + self.assertEquals(NS_PUBSUB_OWNER, element.uri) + self.assertEquals(NS_PUBSUB_OWNER, element.affiliations.uri) + self.assertEquals(u'test', element.affiliations[u'node']) + children = list(element.affiliations.elements()) + self.assertEquals(1, len(children)) + affiliation = children[0] + self.assertEquals(u'affiliation', affiliation.name) + self.assertEquals(NS_PUBSUB_OWNER, affiliation.uri) + self.assertEquals(u'user@example.org', affiliation[u'jid']) + self.assertEquals(u'owner', affiliation[u'affiliation']) + + self.resource.affiliationsGet = affiliationsGet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_affiliationsGetEmptyNode(self): + """ + Getting node affiliations without node should assume empty node. + """ + + xml = """ + + + + + + """ + + def affiliationsGet(request): + self.assertEqual('', request.nodeIdentifier) + return defer.succeed({}) + + def cb(element): + self.assertFalse(element.affiliations.hasAttribute(u'node')) + + self.resource.affiliationsGet = affiliationsGet + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + + def test_on_affiliationsSet(self): + """ + Setting node affiliations has the affiliations to be modified. + """ + + xml = """ + + + + + + + + """ + + def affiliationsSet(request): + self.assertEquals(u'test', request.nodeIdentifier) + otherJID = JID(u'other@example.org') + self.assertIn(otherJID, request.affiliations) + self.assertEquals(u'publisher', request.affiliations[otherJID]) + + self.resource.affiliationsSet = affiliationsSet + return self.handleRequest(xml) + + + def test_on_affiliationsSetBareJID(self): + """ + Affiliations are always on the bare JID. + """ + + xml = """ + + + + + + + + """ + + def affiliationsSet(request): + otherJID = JID(u'other@example.org') + self.assertIn(otherJID, request.affiliations) + + self.resource.affiliationsSet = affiliationsSet + return self.handleRequest(xml) + + + def test_on_affiliationsSetMultipleForSameEntity(self): + """ + Setting node affiliations can only have one item per entity. + """ + + xml = """ + + + + + + + + + """ + + def cb(result): + self.assertEquals('bad-request', result.condition) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_affiliationsSetMissingJID(self): + """ + Setting node affiliations must include a JID per affiliation. + """ + + xml = """ + + + + + + + + """ + + def cb(result): + self.assertEquals('bad-request', result.condition) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_on_affiliationsSetMissingAffiliation(self): + """ + Setting node affiliations must include an affiliation. + """ + + xml = """ + + + + + + + + """ + + def cb(result): + self.assertEquals('bad-request', result.condition) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + +class PubSubServiceWithoutResourceTest(unittest.TestCase, TestableRequestHandlerMixin): + + def setUp(self): + self.stub = XmlStreamStub() + self.service = pubsub.PubSubService() + self.service.send = self.stub.xmlstream.send + + + def test_getDiscoInfo(self): + """ + Test getDiscoInfo calls getNodeInfo and returns some minimal info. + """ + def cb(info): + discoInfo = disco.DiscoInfo() + for item in info: + discoInfo.append(item) + self.assertIn(('pubsub', 'service'), discoInfo.identities) + self.assertIn(disco.NS_DISCO_ITEMS, discoInfo.features) + + d = self.service.getDiscoInfo(JID('user@example.org/home'), + JID('pubsub.example.org'), '') + d.addCallback(cb) + return d + + + def test_publish(self): + """ + Non-overridden L{PubSubService.publish} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('publish', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_subscribe(self): + """ + Non-overridden L{PubSubService.subscribe} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('subscribe', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_unsubscribe(self): + """ + Non-overridden L{PubSubService.unsubscribe} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('subscribe', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_subscriptions(self): + """ + Non-overridden L{PubSubService.subscriptions} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-subscriptions', + result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_affiliations(self): + """ + Non-overridden L{PubSubService.affiliations} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-affiliations', + result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_create(self): + """ + Non-overridden L{PubSubService.create} yields unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('create-nodes', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_getDefaultConfiguration(self): + """ + Non-overridden L{PubSubService.getDefaultConfiguration} yields + unsupported error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-default', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_getConfiguration(self): + """ + Non-overridden L{PubSubService.getConfiguration} yields unsupported + error. + """ + + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('config-node', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_setConfiguration(self): + """ + Non-overridden L{PubSubService.setConfiguration} yields unsupported + error. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('config-node', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_setConfigurationOptionsDict(self): + """ + Options should be passed as a dictionary, not a form. + """ + + xml = """ + + + + + + http://jabber.org/protocol/pubsub#node_config + + 0 + 1 + + + + + """ + + def getConfigurationOptions(): + return { + "pubsub#persist_items": + {"type": "boolean", + "label": "Persist items to storage"}, + "pubsub#deliver_payloads": + {"type": "boolean", + "label": "Deliver payloads with event notifications"} + } + + def setConfiguration(requestor, service, nodeIdentifier, options): + self.assertIn('pubsub#deliver_payloads', options) + self.assertFalse(options['pubsub#deliver_payloads']) + self.assertIn('pubsub#persist_items', options) + self.assertTrue(options['pubsub#persist_items']) + + self.service.getConfigurationOptions = getConfigurationOptions + self.service.setConfiguration = setConfiguration + return self.handleRequest(xml) + + + def test_items(self): + """ + Non-overridden L{PubSubService.items} yields unsupported error. + """ + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-items', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_retract(self): + """ + Non-overridden L{PubSubService.retract} yields unsupported error. + """ + xml = """ + + + + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retract-items', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_purge(self): + """ + Non-overridden L{PubSubService.purge} yields unsupported error. + """ + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('purge-nodes', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_delete(self): + """ + Non-overridden L{PubSubService.delete} yields unsupported error. + """ + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('delete-nodes', result.appCondition['feature']) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_unknown(self): + """ + Unknown verb yields unsupported error. + """ + xml = """ + + + + + + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + + d = self.handleRequest(xml) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + +class PubSubResourceTest(unittest.TestCase): + + def setUp(self): + self.resource = pubsub.PubSubResource() + + + def test_interface(self): + """ + Do instances of L{pubsub.PubSubResource} provide L{iwokkel.IPubSubResource}? + """ + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + + + def test_getNodes(self): + """ + Default getNodes returns an empty list. + """ + def cb(nodes): + self.assertEquals([], nodes) + + d = self.resource.getNodes(JID('user@example.org/home'), + JID('pubsub.example.org'), + '') + d.addCallback(cb) + return d + + + def test_publish(self): + """ + Non-overridden L{PubSubResource.publish} yields unsupported + error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('publish', result.appCondition['feature']) + + d = self.resource.publish(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_subscribe(self): + """ + Non-overridden subscriptions yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('subscribe', result.appCondition['feature']) + + d = self.resource.subscribe(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_unsubscribe(self): + """ + Non-overridden unsubscribe yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('subscribe', result.appCondition['feature']) + + d = self.resource.unsubscribe(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_subscriptions(self): + """ + Non-overridden subscriptions yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-subscriptions', + result.appCondition['feature']) + + d = self.resource.subscriptions(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_affiliations(self): + """ + Non-overridden affiliations yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-affiliations', + result.appCondition['feature']) + + d = self.resource.affiliations(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_create(self): + """ + Non-overridden create yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('create-nodes', result.appCondition['feature']) + + d = self.resource.create(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_default(self): + """ + Non-overridden default yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-default', + result.appCondition['feature']) + + d = self.resource.default(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_configureGet(self): + """ + Non-overridden configureGet yields unsupported + error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('config-node', result.appCondition['feature']) + + d = self.resource.configureGet(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_configureSet(self): + """ + Non-overridden configureSet yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('config-node', result.appCondition['feature']) + + d = self.resource.configureSet(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_items(self): + """ + Non-overridden items yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retrieve-items', result.appCondition['feature']) + + d = self.resource.items(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_retract(self): + """ + Non-overridden retract yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('retract-items', result.appCondition['feature']) + + d = self.resource.retract(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_purge(self): + """ + Non-overridden purge yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('purge-nodes', result.appCondition['feature']) + + d = self.resource.purge(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_delete(self): + """ + Non-overridden delete yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('delete-nodes', result.appCondition['feature']) + + d = self.resource.delete(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_affiliationsGet(self): + """ + Non-overridden owner affiliations get yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('modify-affiliations', + result.appCondition['feature']) + + d = self.resource.affiliationsGet(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d + + + def test_affiliationsSet(self): + """ + Non-overridden owner affiliations set yields unsupported error. + """ + + def cb(result): + self.assertEquals('feature-not-implemented', result.condition) + self.assertEquals('unsupported', result.appCondition.name) + self.assertEquals(NS_PUBSUB_ERRORS, result.appCondition.uri) + self.assertEquals('modify-affiliations', + result.appCondition['feature']) + + d = self.resource.affiliationsSet(pubsub.PubSubRequest()) + self.assertFailure(d, error.StanzaError) + d.addCallback(cb) + return d diff -r 9a4c367b22bb -r 2cd4f0ab9ad1 wokkel/test/test_rsm.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/wokkel/test/test_rsm.py Tue Jul 14 15:22:02 2015 +0200 @@ -0,0 +1,678 @@ +# Copyright (c) Adrien Cossa. +# See LICENSE for details. + +""" +Tests for L{wokkel.rsm}. +""" + +from zope.interface import verify + +from twisted.trial import unittest +from twisted.words.xish import domish +from twisted.words.protocols.jabber.jid import JID +from twisted.words.protocols.jabber.xmlstream import toResponse +from twisted.internet import defer + +from wokkel.generic import parseXml +from wokkel import iwokkel +from wokkel.test.helpers import XmlStreamStub, TestableRequestHandlerMixin + +from sat.tmp.wokkel import pubsub +from sat.tmp.wokkel.rsm import NS_RSM, RSMRequest, RSMResponse, PubSubClient, PubSubService + +import uuid + + +class RSMRequestTest(unittest.TestCase): + """ + Tests for L{rsm.RSMRequest}. + """ + + def test___init__(self): + """ + Fail to initialize a RSMRequest with wrong attribute values. + """ + self.assertRaises(AssertionError, RSMRequest, index=371, after=u'test') + self.assertRaises(AssertionError, RSMRequest, index=371, before=u'test') + self.assertRaises(AssertionError, RSMRequest, before=117) + self.assertRaises(AssertionError, RSMRequest, after=312) + self.assertRaises(AssertionError, RSMRequest, after=u'117', before=u'312') + + def test_parse(self): + """ + Parse a request element asking for the first page. + """ + xml = """ + + Pete + + 1 + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(1, request.max) + self.assertIdentical(None, request.index) + self.assertIdentical(None, request.after) + self.assertIdentical(None, request.before) + + def test_parseSecondPage(self): + """ + Parse a request element asking for a next page. + """ + xml = """ + + Pete + + 3 + peterpan@neverland.lit + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(3, request.max) + self.assertIdentical(None, request.index) + self.assertEqual(u'peterpan@neverland.lit', request.after) + self.assertIdentical(None, request.before) + + def test_parsePreviousPage(self): + """ + Parse a request element asking for a previous page. + """ + xml = """ + + Pete + + 5 + peterpan@pixyland.org + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(5, request.max) + self.assertIdentical(None, request.index) + self.assertIdentical(None, request.after) + self.assertEqual(u'peterpan@pixyland.org', request.before) + + def test_parseLastPage(self): + """ + Parse a request element asking for the last page. + """ + xml = """ + + Pete + + 7 + + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(7, request.max) + self.assertIdentical(None, request.index) + self.assertIdentical(None, request.after) + self.assertEqual('', request.before) + + def test_parseOutOfOrderPage(self): + """ + Parse a request element asking for a page out of order. + """ + xml = """ + + Pete + + 9 + 371 + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(9, request.max) + self.assertEqual(371, request.index) + self.assertIdentical(None, request.after) + self.assertIdentical(None, request.before) + + def test_parseItemCount(self): + """ + Parse a request element asking for the items count. + """ + xml = """ + + Pete + + 0 + + + """ + request = RSMRequest.parse(parseXml(xml)) + self.assertEqual(0, request.max) + self.assertIdentical(None, request.index) + self.assertIdentical(None, request.after) + self.assertIdentical(None, request.before) + + def test_render(self): + """ + Embed a page request in the element. + """ + element = domish.Element(('jabber:iq:search', 'query')) + element.addElement('items')['max_items'] = u'10' + RSMRequest(1).render(element) + + self.assertEqual(u'10', element.items['max_items']) # not changed + + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'1', ''.join(element.set.max.children)) + self.assertIdentical(None, element.set.after) + self.assertIdentical(None, element.set.before) + self.assertIdentical(None, element.set.index) + + def test_renderPubSub(self): + """ + Embed a page request in the pubsub element. + """ + element = domish.Element((pubsub.NS_PUBSUB, 'pubsub')) + element.addElement('items')['max_items'] = u'10' + RSMRequest(3).render(element) + + self.assertEqual(u'3', element.items['max_items']) # changed + + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'3', ''.join(element.set.max.children)) + self.assertIdentical(None, element.set.after) + self.assertIdentical(None, element.set.before) + self.assertIdentical(None, element.set.index) + + def test_renderItems(self): + """ + Embed a page request in the element, specify items. + """ + element = domish.Element(('jabber:iq:search', 'query')) + RSMRequest(5, 127).render(element) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'5', ''.join(element.set.max.children)) + self.assertIdentical(None, element.set.after) + self.assertIdentical(None, element.set.before) + self.assertEqual(u'127', ''.join(element.set.index.children)) + + def test_renderAfter(self): + """ + Embed a page request in the element, specify after. + """ + element = domish.Element(('jabber:iq:search', 'query')) + RSMRequest(5, after=u'test').render(element) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'5', ''.join(element.set.max.children)) + self.assertEqual(u'test', ''.join(element.set.after.children)) + self.assertIdentical(None, element.set.before) + self.assertIdentical(None, element.set.index) + + def test_renderBefore(self): + """ + Embed a page request in the element, specify before. + """ + element = domish.Element(('jabber:iq:search', 'query')) + RSMRequest(5, before=u'test').render(element) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'5', ''.join(element.set.max.children)) + self.assertIdentical(None, element.set.after) + self.assertEqual(u'test', ''.join(element.set.before.children)) + self.assertIdentical(None, element.set.index) + + +class RSMResponseTest(unittest.TestCase): + """ + Tests for L{rsm.RSMResponse}. + """ + + def test___init__(self): + """ + Fail to initialize a RSMResponse with wrong attribute values. + """ + self.assertRaises(AssertionError, RSMResponse, count=u'1') + self.assertRaises(AssertionError, RSMResponse, index=127, first=u'127') + self.assertRaises(AssertionError, RSMResponse, index=127, last=u'351') + self.assertRaises(AssertionError, RSMResponse, first=u'127', last=u'351') + self.assertRaises(AssertionError, RSMResponse, index=u'127', + first=u'127', last=u'351') + self.assertRaises(AssertionError, RSMResponse, index=127, + first=127, last=u'351') + self.assertRaises(AssertionError, RSMResponse, index=127, + first=u'127', last=351) + + def test_parse(self): + """ + Parse a response element returning a page. + """ + xml = """ + + + stpeter@jabber.org + peterpan@neverland.lit + 800 + + + """ + response = RSMResponse.parse(parseXml(xml)) + self.assertEqual(800, response.count) + self.assertEqual(20, response.index) + self.assertEqual(u'stpeter@jabber.org', response.first) + self.assertEqual(u'peterpan@neverland.lit', response.last) + + def test_parseEmptySet(self): + """ + Parse a response element returning an empty set. + """ + xml = """ + + + 800 + + + """ + response = RSMResponse.parse(parseXml(xml)) + self.assertEqual(800, response.count) + self.assertIdentical(None, response.first) + self.assertIdentical(None, response.last) + self.assertIdentical(None, response.index) + + def test_render(self): + """ + Embed a page response in the element. + """ + element = domish.Element(('jabber:iq:search', 'query')) + RSMResponse(800, 20, u'stpeter@jabber.org', + u'peterpan@neverland.lit').render(element) + + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'800', ''.join(element.set.count.children)) + self.assertEqual(u'stpeter@jabber.org', + ''.join(element.set.first.children)) + self.assertEqual(u'peterpan@neverland.lit', + ''.join(element.set.last.children)) + self.assertEqual(u'20', element.set.first['index']) + + def test_renderEmptySet(self): + """ + Embed a page response in the element, for empty set. + """ + element = domish.Element(('jabber:iq:search', 'query')) + RSMResponse(800).render(element) + + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual(u'800', ''.join(element.set.count.children)) + self.assertIdentical(None, element.set.first) + self.assertIdentical(None, element.set.last) + + +class PubSubClientTest(unittest.TestCase): + """ + Tests for L{rsm.PubSubClient}. + """ + timeout = 2 + + def setUp(self): + self.stub = XmlStreamStub() + self.protocol = PubSubClient() + self.protocol.xmlstream = self.stub.xmlstream + self.protocol.connectionInitialized() + + def test_items(self): + """ + Test sending items request to get the first page. + """ + def cb(items): + self.assertEquals(2, len(items)) + self.assertEquals([item1, item2], items) + rsm = self.protocol.getRSMResponse(ext_data['id']) + self.assertDictEqual(rsm, {'count': '800', 'index': '0', + 'first': 'item1', 'last': 'item2'}) + + ext_data = {'id': unicode(uuid.uuid4()), 'rsm': RSMRequest(2)} + d = self.protocol.items(JID('pubsub.example.org'), 'test', + ext_data=ext_data) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(pubsub.NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', pubsub.NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + set_elts = list(domish.generateElementsQNamed(iq.pubsub.children, + 'set', NS_RSM)) + self.assertEquals(1, len(set_elts)) + set_elt = set_elts[0] + self.assertEquals(u'2', ''.join(set_elt.max.children)) + + response = toResponse(iq, 'result') + items = response.addElement((pubsub.NS_PUBSUB, + 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item1' + item2 = items.addElement('item') + item2['id'] = 'item2' + RSMResponse(800, 0, u'item1', u'item2').render(response.pubsub) + self.stub.send(response) + + return d + + def test_itemsAfter(self): + """ + Test sending items request to get the next page. + """ + def cb(items): + self.assertEquals(2, len(items)) + self.assertEquals([item1, item2], items) + rsm = self.protocol.getRSMResponse(ext_data['id']) + self.assertDictEqual(rsm, {'count': '800', 'index': '2', + 'first': 'item3', 'last': 'item4'}) + + ext_data = {'id': unicode(uuid.uuid4()), + 'rsm': RSMRequest(2, after=u'item2')} + d = self.protocol.items(JID('pubsub.example.org'), 'test', + ext_data=ext_data) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(pubsub.NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', pubsub.NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + set_elts = list(domish.generateElementsQNamed(iq.pubsub.children, + 'set', NS_RSM)) + self.assertEquals(1, len(set_elts)) + set_elt = set_elts[0] + self.assertEquals(u'2', ''.join(set_elt.max.children)) + self.assertEquals(u'item2', ''.join(set_elt.after.children)) + + response = toResponse(iq, 'result') + items = response.addElement((pubsub.NS_PUBSUB, + 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item3' + item2 = items.addElement('item') + item2['id'] = 'item4' + RSMResponse(800, 2, u'item3', u'item4').render(response.pubsub) + self.stub.send(response) + + return d + + def test_itemsBefore(self): + """ + Test sending items request to get the previous page. + """ + def cb(items): + self.assertEquals(2, len(items)) + self.assertEquals([item1, item2], items) + rsm = self.protocol.getRSMResponse(ext_data['id']) + self.assertDictEqual(rsm, {'count': '800', 'index': '0', + 'first': 'item1', 'last': 'item2'}) + + ext_data = {'id': unicode(uuid.uuid4()), + 'rsm': RSMRequest(2, before=u'item3')} + d = self.protocol.items(JID('pubsub.example.org'), 'test', + ext_data=ext_data) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(pubsub.NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', pubsub.NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + set_elts = list(domish.generateElementsQNamed(iq.pubsub.children, + 'set', NS_RSM)) + self.assertEquals(1, len(set_elts)) + set_elt = set_elts[0] + self.assertEquals(u'2', ''.join(set_elt.max.children)) + self.assertEquals(u'item3', ''.join(set_elt.before.children)) + + response = toResponse(iq, 'result') + items = response.addElement((pubsub.NS_PUBSUB, + 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item1' + item2 = items.addElement('item') + item2['id'] = 'item2' + RSMResponse(800, 0, u'item1', u'item2').render(response.pubsub) + self.stub.send(response) + + return d + + def test_itemsIndex(self): + """ + Test sending items request to get a page out of order. + """ + def cb(items): + self.assertEquals(3, len(items)) + self.assertEquals([item1, item2, item3], items) + rsm = self.protocol.getRSMResponse(ext_data['id']) + self.assertDictEqual(rsm, {'count': '800', 'index': '3', + 'first': 'item4', 'last': 'item6'}) + + ext_data = {'id': unicode(uuid.uuid4()), 'rsm': RSMRequest(3, 3)} + d = self.protocol.items(JID('pubsub.example.org'), 'test', + ext_data=ext_data) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(pubsub.NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', pubsub.NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + set_elts = list(domish.generateElementsQNamed(iq.pubsub.children, + 'set', NS_RSM)) + self.assertEquals(1, len(set_elts)) + set_elt = set_elts[0] + self.assertEquals(u'3', ''.join(set_elt.max.children)) + self.assertEquals(u'3', ''.join(set_elt.index.children)) + + response = toResponse(iq, 'result') + items = response.addElement((pubsub.NS_PUBSUB, + 'pubsub')).addElement('items') + items['node'] = 'test' + item1 = items.addElement('item') + item1['id'] = 'item4' + item2 = items.addElement('item') + item2['id'] = 'item5' + item3 = items.addElement('item') + item3['id'] = 'item6' + RSMResponse(800, 3, u'item4', u'item6').render(response.pubsub) + self.stub.send(response) + + return d + + def test_itemsCount(self): + """ + Test sending items request to count them. + """ + def cb(items): + self.assertEquals(0, len(items)) + rsm = self.protocol.getRSMResponse(ext_data['id']) + self.assertDictEqual(rsm, {'count': '800'}) + + ext_data = {'id': unicode(uuid.uuid4()), 'rsm': RSMRequest(0)} + d = self.protocol.items(JID('pubsub.example.org'), 'test', + ext_data=ext_data) + d.addCallback(cb) + + iq = self.stub.output[-1] + self.assertEquals('pubsub.example.org', iq.getAttribute('to')) + self.assertEquals('get', iq.getAttribute('type')) + self.assertEquals('pubsub', iq.pubsub.name) + self.assertEquals(pubsub.NS_PUBSUB, iq.pubsub.uri) + children = list(domish.generateElementsQNamed(iq.pubsub.children, + 'items', pubsub.NS_PUBSUB)) + self.assertEquals(1, len(children)) + child = children[0] + self.assertEquals('test', child['node']) + + set_elts = list(domish.generateElementsQNamed(iq.pubsub.children, + 'set', NS_RSM)) + self.assertEquals(1, len(set_elts)) + set_elt = set_elts[0] + self.assertEquals(u'0', ''.join(set_elt.max.children)) + + response = toResponse(iq, 'result') + response.addElement((pubsub.NS_PUBSUB, 'pubsub')) + RSMResponse(800).render(response.pubsub) + self.stub.send(response) + + return d + + +class PubSubServiceTest(unittest.TestCase, TestableRequestHandlerMixin): + + def setUp(self): + self.stub = XmlStreamStub() + self.resource = pubsub.PubSubResource() + self.service = PubSubService(self.resource) + self.service.send = self.stub.xmlstream.send + + def test_on_items(self): + """ + On a items request, return the first item for the given node. + """ + xml = """ + + + + + + 1 + + + """ + + def items(request): + rsm = RSMResponse(800, 0, u'item', u'item').toElement() + return defer.succeed([pubsub.Item('current'), rsm]) + + def cb(element): + self.assertEqual(pubsub.NS_PUBSUB, element.uri) + self.assertEqual(pubsub.NS_PUBSUB, element.items.uri) + self.assertEqual(1, len(element.items.children)) + item = element.items.children[-1] + self.assertTrue(domish.IElement.providedBy(item)) + self.assertEqual('item', item.name) + self.assertEqual(pubsub.NS_PUBSUB, item.uri) + self.assertEqual('current', item['id']) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual('800', ''.join(element.set.count.children)) + self.assertEqual('0', element.set.first['index']) + self.assertEqual('item', ''.join(element.set.first.children)) + self.assertEqual('item', ''.join(element.set.last.children)) + + self.resource.items = items + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + def test_on_itemsIndex(self): + """ + On a items request, return some items out of order for the given node. + """ + xml = """ + + + + + + 2 + 3 + + + """ + + def items(request): + rsm = RSMResponse(800, 3, u'i1', u'i2').toElement() + return defer.succeed([pubsub.Item('i1'), pubsub.Item('i2'), rsm]) + + def cb(element): + self.assertEqual(pubsub.NS_PUBSUB, element.uri) + self.assertEqual(pubsub.NS_PUBSUB, element.items.uri) + self.assertEqual(2, len(element.items.children)) + item = element.items.children[0] + self.assertTrue(domish.IElement.providedBy(item)) + self.assertEqual('item', item.name) + self.assertEqual(pubsub.NS_PUBSUB, item.uri) + self.assertEqual('i1', item['id']) + item = element.items.children[1] + self.assertTrue(domish.IElement.providedBy(item)) + self.assertEqual('item', item.name) + self.assertEqual(pubsub.NS_PUBSUB, item.uri) + self.assertEqual('i2', item['id']) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual('800', ''.join(element.set.count.children)) + self.assertEqual('3', element.set.first['index']) + self.assertEqual('i1', ''.join(element.set.first.children)) + self.assertEqual('i2', ''.join(element.set.last.children)) + + self.resource.items = items + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d + + def test_on_itemsCount(self): + """ + On a items request, return the items count. + """ + xml = """ + + + + + + 0 + + + """ + + def items(request): + rsm = RSMResponse(800).toElement() + return defer.succeed([rsm]) + + def cb(element): + self.assertEqual(pubsub.NS_PUBSUB, element.uri) + self.assertEqual(pubsub.NS_PUBSUB, element.items.uri) + self.assertEqual(0, len(element.items.children)) + self.assertEqual(NS_RSM, element.set.uri) + self.assertEqual('800', ''.join(element.set.count.children)) + self.assertEqual(None, element.set.first) + self.assertEqual(None, element.set.last) + + self.resource.items = items + verify.verifyObject(iwokkel.IPubSubResource, self.resource) + d = self.handleRequest(xml) + d.addCallback(cb) + return d