tmp: removed sat.tmp hierarchy and fixed references to it as it is now an independant sat_tmp repository

This commit is contained in:
Goffi 2017-11-03 14:19:10 +01:00
commit a5f28ef7dd
16 changed files with 11 additions and 7660 deletions

View file

@ -295,8 +295,7 @@ setup(name=NAME,
package_dir={'sat': 'src', 'sat_frontends': 'frontends/src', 'twisted.plugins': 'src/twisted/plugins'},
packages=['sat', 'sat.tools', 'sat.tools.common', 'sat.bridge', 'sat.plugins', 'sat.test', 'sat.core', 'sat.memory',
'sat_frontends', 'sat_frontends.bridge', 'sat_frontends.quick_frontend', 'sat_frontends.jp',
'sat_frontends.primitivus', 'sat_frontends.tools', 'sat.stdui','sat.tmp', 'sat.tmp.wokkel',
'twisted.plugins'],
'sat_frontends.primitivus', 'sat_frontends.tools', 'sat.stdui', 'twisted.plugins'],
package_data={'sat': ['sat.sh'], },
data_files=[(os.path.join(sys.prefix, 'share/locale/fr/LC_MESSAGES'), ['i18n/fr/LC_MESSAGES/sat.mo']),
('share/doc/%s' % NAME, ['CHANGELOG', 'COPYING', 'INSTALL', 'README', 'README4TRANSLATORS']),
@ -304,6 +303,6 @@ setup(name=NAME,
],
scripts=['frontends/src/jp/jp', 'frontends/src/primitivus/primitivus', ],
zip_safe=False,
install_requires=['twisted >= 15.2.0', 'wokkel >= 0.7.1', 'progressbar', 'urwid >= 1.2.0', 'urwid-satext >= 0.6.1', 'mutagen', 'pillow', 'lxml >= 3.1.0', 'pyxdg', 'markdown', 'html2text', 'pycrypto >= 2.6.1', 'python-potr', 'PyOpenSSL', 'service_identity', 'shortuuid', 'babel'],
install_requires=['twisted >= 15.2.0', 'wokkel >= 0.7.1', 'sat_tmp', 'progressbar', 'urwid >= 1.2.0', 'urwid-satext >= 0.6.1', 'mutagen', 'pillow', 'lxml >= 3.1.0', 'pyxdg', 'markdown', 'html2text', 'pycrypto >= 2.6.1', 'python-potr', 'PyOpenSSL', 'service_identity', 'shortuuid', 'babel'],
cmdclass={'install': CustomInstall},
)

View file

@ -1,7 +1,7 @@
# FIXME: remove this when RSM and MAM are in wokkel
# XXX: the Monkey Patch is here and not in src/__init__ to avoir issues with pyjamas compilation
import wokkel
from sat.tmp.wokkel import pubsub as tmp_pubsub, rsm as tmp_rsm, mam as tmp_mam
from sat_tmp.wokkel import pubsub as tmp_pubsub, rsm as tmp_rsm, mam as tmp_mam
wokkel.pubsub = tmp_pubsub
wokkel.rsm = tmp_rsm
wokkel.mam = tmp_mam

View file

@ -33,8 +33,8 @@ from collections import namedtuple
import urllib
import datetime
from dateutil import tz
# XXX: tmp.wokkel.pubsub is actually use instead of wokkel version
# mam and rsm come from tmp.wokkel too
# XXX: sat_tmp.wokkel.pubsub is actually use instead of wokkel version
# mam and rsm come from sat_tmp.wokkel too
from wokkel import pubsub
from wokkel import rsm
from wokkel import mam

View file

@ -32,7 +32,7 @@ from sat.tools import sat_defer
from sat.tools import utils
from sat.tools.common import data_format
# XXX: tmp.pubsub is actually used instead of wokkel version
# XXX: sat_tmp.wokkel.pubsub is actually used instead of wokkel version
from wokkel import pubsub
from wokkel import disco, iwokkel
from zope.interface import implements
@ -662,7 +662,7 @@ class XEP_0277(object):
@param access: Node access model, according to xep-0060 #4.5
@param profile_key: profile key
"""
# FIXME: check if this mehtod is needed, deprecate it if not
# FIXME: check if this mehtod is need, deprecate it if not
client = self.host.getClient(profile_key)
_options = {self._p.OPT_ACCESS_MODEL: access, self._p.OPT_PERSIST_ITEMS: 1, self._p.OPT_MAX_ITEMS: -1, self._p.OPT_DELIVER_PAYLOADS: 1, self._p.OPT_SEND_ITEM_SUBSCRIBE: 1}

View file

@ -31,7 +31,7 @@ from zope.interface import implements
from wokkel import disco
import uuid
# XXX: mam and rsm come from tmp.wokkel
# XXX: mam and rsm come from sat_tmp.wokkel
from wokkel import rsm
from wokkel import mam

View file

@ -27,7 +27,7 @@ from wokkel.generic import parseXml
from wokkel.disco import DiscoItem, DiscoItems
# temporary until the changes are integrated to Wokkel
from sat.tmp.wokkel.rsm import RSMResponse
from sat_tmp.wokkel.rsm import RSMResponse
from constants import Const as C
from sat.plugins import plugin_xep_0045

View file

@ -30,8 +30,8 @@ from dateutil.tz import tzutc
import datetime
# TODO: change this when RSM and MAM are in wokkel
from sat.tmp.wokkel.rsm import RSMRequest
from sat.tmp.wokkel.mam import buildForm, MAMRequest
from sat_tmp.wokkel.rsm import RSMRequest
from sat_tmp.wokkel.mam import buildForm, MAMRequest
NS_PUBSUB = 'http://jabber.org/protocol/pubsub'
SERVICE = 'sat-pubsub.tazar.int'

View file

@ -1,6 +0,0 @@
Use this module to temporary store files that need to be integrated to other
projects such as Wokkel.
For example, the changeset that introduced this folder adds RSM (XEP-0059)
support to Wokkel. The files in sat.tmp.wokkel are imported over the initial
wokkel files from sat.plugins.__init__.py

View file

View file

@ -1,620 +0,0 @@
# -*- coding: utf-8 -*-
# -*- test-case-name: wokkel.test.test_mam -*-
#
# SàT Wokkel extension for Message Archive Management (XEP-0313)
# Copyright (C) 2015 Jérôme Poisson (goffi@goffi.org)
# Copyright (C) 2015 Adien Cossa (souliane@mailoo.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
XMPP Message Archive Management protocol.
This protocol is specified in
U{XEP-0313<http://xmpp.org/extensions/xep-0313.html>}.
"""
from dateutil import tz
from zope.interface import implements
from zope.interface import Interface
from twisted.words.protocols.jabber import xmlstream
from twisted.words.xish import domish
from twisted.words.protocols.jabber import jid
from twisted.words.protocols.jabber import error
from twisted.internet import defer
from twisted.python import log
from wokkel import subprotocols
from wokkel import disco
from wokkel import data_form
from wokkel import delay
import rsm
NS_MAM = 'urn:xmpp:mam:1'
NS_FORWARD = 'urn:xmpp:forward:0'
FIELDS_REQUEST = "/iq[@type='get']/query[@xmlns='%s']" % NS_MAM
ARCHIVE_REQUEST = "/iq[@type='set']/query[@xmlns='%s']" % NS_MAM
PREFS_GET_REQUEST = "/iq[@type='get']/prefs[@xmlns='%s']" % NS_MAM
PREFS_SET_REQUEST = "/iq[@type='set']/prefs[@xmlns='%s']" % NS_MAM
# TODO: add the tests!
class MAMError(error.StanzaError):
"""
MAM error.
"""
def __init__(self, text=None):
error.StanzaError.__init__(self, 'bad-request', text=text)
class Unsupported(MAMError):
def __init__(self, feature, text=None):
self.feature = feature
MAMError.__init__(self, 'feature-not-implemented',
'unsupported',
feature,
text)
def __str__(self):
message = MAMError.__str__(self)
message += ', feature %r' % self.feature
return message
class MAMRequest(object):
"""
A Message Archive Management <query/> request.
@ivar form: Data Form specifing the filters.
@itype form: L{data_form.Form}
@ivar rsm: RSM request instance.
@itype rsm: L{rsm.RSMRequest}
@ivar node: pubsub node id if querying a pubsub node, else None.
@itype node: C{unicode}
@ivar query_id: id to use to track the query
@itype query_id: C{unicode}
"""
# FIXME: should be based on generic.Stanza
def __init__(self, form=None, rsm_=None, node=None, query_id=None, sender=None, recipient=None):
if form is not None:
assert form.formType == 'submit'
assert form.formNamespace == NS_MAM
self.form = form
self.rsm = rsm_
self.node = node
self.query_id = query_id
self.sender = sender
self.recipient = recipient
@classmethod
def fromElement(cls, iq):
"""Parse the DOM representation of a MAM <query/> request.
@param iq: <iq/> element containing a MAM <query/>.
@type iq: L{Element<twisted.words.xish.domish.Element>}
@return: MAMRequest instance.
@rtype: L{MAMRequest}
"""
sender = jid.JID(iq.getAttribute('from'))
recipient = jid.JID(iq.getAttribute('to'))
try:
query = iq.elements(NS_MAM, 'query').next()
except StopIteration:
raise MAMError("Can't find MAM <query/> in element")
form = data_form.findForm(query, NS_MAM)
try:
rsm_request = rsm.RSMRequest.fromElement(query)
except rsm.RSMNotFoundError:
rsm_request = None
node = query.getAttribute('node')
query_id = query.getAttribute('queryid')
return MAMRequest(form, rsm_request, node, query_id, sender, recipient)
def toElement(self):
"""
Return the DOM representation of this RSM <query/> request.
@rtype: L{Element<twisted.words.xish.domish.Element>}
"""
mam_elt = domish.Element((NS_MAM, 'query'))
if self.node is not None:
mam_elt['node'] = self.node
if self.query_id is not None:
mam_elt['queryid'] = self.query_id
if self.form is not None:
mam_elt.addChild(self.form.toElement())
if self.rsm is not None:
mam_elt.addChild(self.rsm.toElement())
return mam_elt
def render(self, parent):
"""Embed the DOM representation of this MAM request in the given element.
@param parent: parent IQ element.
@type parent: L{Element<twisted.words.xish.domish.Element>}
@return: MAM request element.
@rtype: L{Element<twisted.words.xish.domish.Element>}
"""
assert parent.name == 'iq'
mam_elt = self.toElement()
parent.addChild(mam_elt)
return mam_elt
class MAMPrefs(object):
"""
A Message Archive Management <prefs/> request.
@param default: A value in ('always', 'never', 'roster').
@type : C{unicode} or C{None}
@param always (list): A list of JID instances.
@type always: C{list}
@param never (list): A list of JID instances.
@type never: C{list}
"""
def __init__(self, default=None, always=None, never=None):
if default is not None:
# default must be defined in response, but can be empty in request (see http://xmpp.org/extensions/xep-0313.html#config)
assert default in ('always', 'never', 'roster')
self.default = default
if always is not None:
assert isinstance(always, list)
else:
always = []
self.always = always
if never is not None:
assert isinstance(never, list)
else:
never = []
self.never = never
@classmethod
def fromElement(cls, prefs_elt):
"""Parse the DOM representation of a MAM <prefs/> request.
@param prefs_elt: MAM <prefs/> request element.
@type prefs_elt: L{Element<twisted.words.xish.domish.Element>}
@return: MAMPrefs instance.
@rtype: L{MAMPrefs}
"""
if prefs_elt.uri != NS_MAM or prefs_elt.name != 'prefs':
raise MAMError('Element provided is not a MAM <prefs/> request')
try:
default = prefs_elt['default']
except KeyError:
# FIXME: return proper error here
raise MAMError('Element provided is not a valid MAM <prefs/> request')
prefs = {}
for attr in ('always', 'never'):
prefs[attr] = []
try:
pref = prefs_elt.elements(NS_MAM, attr).next()
except StopIteration:
# FIXME: return proper error here
raise MAMError('Element provided is not a valid MAM <prefs/> request')
else:
for jid_s in pref.elements(NS_MAM, 'jid'):
prefs[attr].append(jid.JID(jid_s))
return MAMPrefs(default, **prefs)
def toElement(self):
"""
Return the DOM representation of this RSM <prefs/>request.
@rtype: L{Element<twisted.words.xish.domish.Element>}
"""
mam_elt = domish.Element((NS_MAM, 'prefs'))
if self.default:
mam_elt['default'] = self.default
for attr in ('always', 'never'):
attr_elt = mam_elt.addElement(attr)
jids = getattr(self, attr)
for jid_ in jids:
attr_elt.addElement('jid', content=jid_.full())
return mam_elt
def render(self, parent):
"""Embed the DOM representation of this MAM request in the given element.
@param parent: parent IQ element.
@type parent: L{Element<twisted.words.xish.domish.Element>}
@return: MAM request element.
@rtype: L{Element<twisted.words.xish.domish.Element>}
"""
assert parent.name == 'iq'
mam_elt = self.toElement()
parent.addChild(mam_elt)
return mam_elt
class MAMClient(subprotocols.XMPPHandler):
"""
MAM client.
This handler implements the protocol for sending out MAM requests.
"""
def queryArchive(self, mam_query, service=None, sender=None):
"""Query a user, MUC or pubsub archive.
@param mam_query: query to use
@type form: L{MAMRequest}
@param service: Entity offering the MAM service (None for user server).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param sender: Optional sender address.
@type sender: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: A deferred that fires upon receiving a response.
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
iq = xmlstream.IQ(self.xmlstream, 'set')
mam_query.render(iq)
if sender is not None:
iq['from'] = unicode(sender)
return iq.send(to=service.full() if service else None)
def queryFields(self, service=None, sender=None):
"""Ask the server about supported fields.
@param service: Entity offering the MAM service (None for user archives).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param sender: Optional sender address.
@type sender: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: data Form with the fields, or None if not found
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
# http://xmpp.org/extensions/xep-0313.html#query-form
iq = xmlstream.IQ(self.xmlstream, 'get')
MAMRequest().render(iq)
if sender is not None:
iq['from'] = unicode(sender)
d = iq.send(to=service.full() if service else None)
d.addCallback(lambda iq_result: iq_result.elements(NS_MAM, 'query').next())
d.addCallback(data_form.findForm, NS_MAM)
return d
def queryPrefs(self, service=None, sender=None):
"""Retrieve the current user preferences.
@param service: Entity offering the MAM service (None for user archives).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param sender: Optional sender address.
@type sender: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: A deferred that fires upon receiving a response.
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
# http://xmpp.org/extensions/xep-0313.html#prefs
iq = xmlstream.IQ(self.xmlstream, 'get')
MAMPrefs().render(iq)
if sender is not None:
iq['from'] = unicode(sender)
return iq.send(to=service.full() if service else None)
def setPrefs(self, service=None, default='roster', always=None, never=None, sender=None):
"""Set new user preferences.
@param service: Entity offering the MAM service (None for user archives).
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param default: A value in ('always', 'never', 'roster').
@type : C{unicode}
@param always (list): A list of JID instances.
@type always: C{list}
@param never (list): A list of JID instances.
@type never: C{list}
@param sender: Optional sender address.
@type sender: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: A deferred that fires upon receiving a response.
@rtype: L{Deferred<twisted.internet.defer.Deferred>}
"""
# http://xmpp.org/extensions/xep-0313.html#prefs
assert default is not None
iq = xmlstream.IQ(self.xmlstream, 'set')
MAMPrefs(default, always, never).render(iq)
if sender is not None:
iq['from'] = unicode(sender)
return iq.send(to=service.full() if service else None)
class IMAMResource(Interface):
def onArchiveRequest(self, mam):
"""
@param mam: The MAM <query/> request.
@type mam: L{MAMQueryReques<wokkel.mam.MAMRequest>}
@return: The RSM answer.
@rtype: L{RSMResponse<wokkel.rsm.RSMResponse>}
"""
def onPrefsGetRequest(self, requestor):
"""
@param requestor: JID of the requestor.
@type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: The current settings.
@rtype: L{wokkel.mam.MAMPrefs}
"""
def onPrefsSetRequest(self, prefs, requestor):
"""
@param prefs: The new settings to set.
@type prefs: L{wokkel.mam.MAMPrefs}
@param requestor: JID of the requestor.
@type requestor: L{JID<twisted.words.protocols.jabber.jid.JID>}
@return: The new current settings.
@rtype: L{wokkel.mam.MAMPrefs}
"""
class IMAMService(Interface):
"""
Interface for XMPP MAM service.
"""
def addFilter(self, field):
"""
Add a new filter for querying MAM archive.
@param field: data form field of the filter
@type field: L{Form<wokkel.data_form.Field>}
"""
class MAMService(subprotocols.XMPPHandler, subprotocols.IQHandlerMixin):
"""
Protocol implementation for a MAM service.
This handler waits for XMPP Ping requests and sends a response.
"""
implements(IMAMService, disco.IDisco)
_request_class = MAMRequest
iqHandlers = {FIELDS_REQUEST: '_onFieldsRequest',
ARCHIVE_REQUEST: '_onArchiveRequest',
PREFS_GET_REQUEST: '_onPrefsGetRequest',
PREFS_SET_REQUEST: '_onPrefsSetRequest'
}
_legacyFilters = {'start': {'fieldType': 'text-single',
'var': 'start',
'label': 'Starting time',
'desc': 'Starting time a the result period.',
},
'end': {'fieldType': 'text-single',
'var': 'end',
'label': 'Ending time',
'desc': 'Ending time of the result period.',
},
'with': {'fieldType': 'jid-single',
'var': 'with',
'label': 'Entity',
'desc': 'Entity against which to match message.',
},
}
def __init__(self, resource):
"""
@param resource: instance implementing IMAMResource
@type resource: L{object}
"""
self.resource = resource
self.extra_fields = {}
def connectionInitialized(self):
"""
Called when the XML stream has been initialized.
This sets up an observer for incoming ping requests.
"""
self.xmlstream.addObserver(FIELDS_REQUEST, self.handleRequest)
self.xmlstream.addObserver(ARCHIVE_REQUEST, self.handleRequest)
self.xmlstream.addObserver(PREFS_GET_REQUEST, self.handleRequest)
self.xmlstream.addObserver(PREFS_SET_REQUEST, self.handleRequest)
def addFilter(self, field):
"""
Add a new filter for querying MAM archive.
@param field: data form field of the filter
@type field: L{Form<wokkel.data_form.Field>}
"""
self.extra_fields[field.var] = field
def _onFieldsRequest(self, iq):
"""
Called when a fields request has been received.
This immediately replies with a result response.
"""
iq.handled = True
query = domish.Element((NS_MAM, 'query'))
query.addChild(buildForm(extra_fields=self.extra_fields).toElement(), formType='form')
return query
def _onArchiveRequest(self, iq):
"""
Called when a message archive request has been received.
This replies with the list of archived message and the <iq> result
@return: A tuple with list of message data (id, element, data) and RSM element
@rtype: C{tuple}
"""
iq.handled = True
mam_ = self._request_class.fromElement(iq)
# remove unsupported filters
unsupported_fields = []
if mam_.form:
for key, field in mam_.form.fields.iteritems():
if key not in self._legacyFilters and key not in self.extra_fields:
log.msg('Ignored unsupported MAM filter: %s' % field)
unsupported_fields.append(key)
for key in unsupported_fields:
del mam_.form.fields[key]
def forwardMessage(id_, elt, date):
msg = domish.Element((None, 'message'))
msg['to'] = iq['from']
result = msg.addElement((NS_MAM, 'result'))
if mam_.query_id is not None:
result['queryid'] = mam_.query_id
result['id'] = id_
forward = result.addElement((NS_FORWARD, 'forwarded'))
forward.addChild(delay.Delay(date).toElement())
forward.addChild(elt)
self.xmlstream.send(msg)
def cb(result):
msg_data, rsm_elt = result
for data in msg_data:
forwardMessage(*data)
fin_elt = domish.Element((NS_MAM, 'fin'))
if rsm_elt is not None:
fin_elt.addChild(rsm_elt)
return fin_elt
d = defer.maybeDeferred(self.resource.onArchiveRequest, mam_)
d.addCallback(cb)
return d
def _onPrefsGetRequest(self, iq):
"""
Called when a prefs get request has been received.
This immediately replies with a result response.
"""
iq.handled = True
requestor = jid.JID(iq['from'])
def cb(prefs):
return prefs.toElement()
d = self.resource.onPrefsGetRequest(requestor).addCallback(cb)
return d
def _onPrefsSetRequest(self, iq):
"""
Called when a prefs get request has been received.
This immediately replies with a result response.
"""
iq.handled = True
prefs = MAMPrefs.fromElement(iq.prefs)
requestor = jid.JID(iq['from'])
def cb(prefs):
return prefs.toElement()
d = self.resource.onPrefsSetRequest(prefs, requestor).addCallback(cb)
return d
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
if nodeIdentifier:
return []
return [disco.DiscoFeature(NS_MAM)]
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
return []
def datetime2utc(datetime_obj):
"""Convert a datetime to a XEP-0082 compliant UTC datetime.
@param datetime_obj: Offset-aware timestamp to convert.
@type datetime_obj: L{datetime<datetime.datetime>}
@return: The datetime converted to UTC.
@rtype: C{unicode}
"""
stampFormat = '%Y-%m-%dT%H:%M:%SZ'
return datetime_obj.astimezone(tz.tzutc()).strftime(stampFormat)
def buildForm(start=None, end=None, with_jid=None, extra_fields=None, formType='submit'):
"""Prepare a Data Form for MAM.
@param start: Offset-aware timestamp to filter out older messages.
@type start: L{datetime<datetime.datetime>}
@param end: Offset-aware timestamp to filter out later messages.
@type end: L{datetime<datetime.datetime>}
@param with_jid: JID against which to match messages.
@type with_jid: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param extra_fields: list of extra data form fields that are not defined by the
specification.
@type: C{list}
@param formType: The type of the Data Form ('submit' or 'form').
@type formType: C{unicode}
@return: XEP-0004 Data Form object.
@rtype: L{Form<wokkel.data_form.Form>}
"""
form = data_form.Form(formType, formNamespace=NS_MAM)
if formType == 'form':
for kwargs in MAMService._legacyFilters.values():
form.addField(data_form.Field(**kwargs))
elif formType == 'submit':
if start:
form.addField(data_form.Field(var='start', value=datetime2utc(start)))
if end:
form.addField(data_form.Field(var='end', value=datetime2utc(end)))
if with_jid:
form.addField(data_form.Field(fieldType='jid-single', var='with', value=with_jid.full()))
if extra_fields is not None:
for field in extra_fields:
form.addField(field)
return form

File diff suppressed because it is too large Load diff

View file

@ -1,425 +0,0 @@
# -*- coding: utf-8 -*-
# -*- test-case-name: wokkel.test.test_rsm -*-
#
# SàT Wokkel extension for Result Set Management (XEP-0059)
# Copyright (C) 2015 Adien Cossa (souliane@mailoo.org)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
XMPP Result Set Management protocol.
This protocol is specified in
U{XEP-0059<http://xmpp.org/extensions/xep-0059.html>}.
"""
from twisted.words.xish import domish
from twisted.words.protocols.jabber import error
import pubsub
import copy
NS_RSM = 'http://jabber.org/protocol/rsm'
class RSMError(error.StanzaError):
"""
RSM error.
"""
def __init__(self, text=None):
error.StanzaError.__init__(self, 'bad-request', text=text)
class RSMNotFoundError(Exception):
"""
An expected RSM element has not been found.
"""
class RSMRequest(object):
"""
A Result Set Management request.
@ivar max_: limit on the number of retrieved items.
@itype max_: C{int} or C{unicode}
@ivar index: starting index of the requested page.
@itype index: C{int} or C{unicode} or C{None}
@ivar after: ID of the element immediately preceding the page.
@itype after: C{unicode}
@ivar before: ID of the element immediately following the page.
@itype before: C{unicode}
"""
def __init__(self, max_=10, after=None, before=None, index=None):
self.max = int(max_)
if index is not None:
assert after is None and before is None
index = int(index)
self.index = index
if after is not None:
assert before is None
assert isinstance(after, basestring)
self.after = after
if before is not None:
assert isinstance(before, basestring)
self.before = before
def __str__(self):
return "RSM Request: max={0.max} after={0.after} before={0.before} index={0.index}".format(self)
@classmethod
def fromElement(cls, element):
"""Parse the given request element.
@param element: request containing a set element, or set element itself.
@type element: L{domish.Element}
@return: RSMRequest instance.
@rtype: L{RSMRequest}
"""
if element.name == 'set' and element.uri == NS_RSM:
set_elt = element
else:
try:
set_elt = element.elements(NS_RSM, 'set').next()
except StopIteration:
raise RSMNotFoundError()
try:
before_elt = set_elt.elements(NS_RSM, 'before').next()
except StopIteration:
before = None
else:
before = unicode(before_elt)
try:
after_elt = set_elt.elements(NS_RSM, 'after').next()
except StopIteration:
after = None
else:
after = unicode(after_elt)
if not after:
raise RSMError("<after/> element can't be empty in RSM request")
try:
max_elt = set_elt.elements(NS_RSM, 'max').next()
except StopIteration:
# FIXME: even if it doesn't make a lot of sense without it
# <max/> element is not mandatory in XEP-0059
raise RSMError("RSM request is missing its 'max' element")
else:
try:
max_ = int(unicode(max_elt))
except ValueError:
raise RSMError("bad value for 'max' element")
try:
index_elt = set_elt.elements(NS_RSM, 'index').next()
except StopIteration:
index = None
else:
try:
index = int(unicode(index_elt))
except ValueError:
raise RSMError("bad value for 'index' element")
return RSMRequest(max_, after, before, index)
def toElement(self):
"""
Return the DOM representation of this RSM request.
@rtype: L{domish.Element}
"""
set_elt = domish.Element((NS_RSM, 'set'))
set_elt.addElement('max', content=unicode(self.max))
if self.index is not None:
set_elt.addElement('index', content=unicode(self.index))
if self.before is not None:
if self.before == '': # request the last page
set_elt.addElement('before')
else:
set_elt.addElement('before', content=self.before)
if self.after is not None:
set_elt.addElement('after', content=self.after)
return set_elt
def render(self, element):
"""Embed the DOM representation of this RSM request in the given element.
@param element: Element to contain the RSM request.
@type element: L{domish.Element}
@return: RSM request element.
@rtype: L{domish.Element}
"""
set_elt = self.toElement()
element.addChild(set_elt)
return set_elt
class RSMResponse(object):
"""
A Result Set Management response.
@ivar first: ID of the first element of the returned page.
@itype first: C{unicode}
@ivar last: ID of the last element of the returned page.
@itype last: C{unicode}
@ivar index: starting index of the returned page.
@itype index: C{int}
@ivar count: total number of items.
@itype count: C{int}
"""
def __init__(self, first=None, last=None, index=None, count=None):
if first is None:
assert last is None and index is None
if last is None:
assert first is None
self.first = first
self.last = last
if count is not None:
self.count = int(count)
else:
self.count = None
if index is not None:
self.index = int(index)
else:
self.index = None
def __str__(self):
return "RSM Request: first={0.first} last={0.last} index={0.index} count={0.count}".format(self)
@classmethod
def fromElement(cls, element):
"""Parse the given response element.
@param element: response element.
@type element: L{domish.Element}
@return: RSMResponse instance.
@rtype: L{RSMResponse}
"""
try:
set_elt = element.elements(NS_RSM, 'set').next()
except StopIteration:
raise RSMNotFoundError()
try:
first_elt = set_elt.elements(NS_RSM, 'first').next()
except StopIteration:
first = None
index = None
else:
first = unicode(first_elt)
try:
index = int(first_elt['index'])
except KeyError:
index = None
except ValueError:
raise RSMError("bad index in RSM response")
try:
last_elt = set_elt.elements(NS_RSM, 'last').next()
except StopIteration:
if first is not None:
raise RSMError("RSM response is missing its 'last' element")
else:
last = None
else:
if first is None:
raise RSMError("RSM response is missing its 'first' element")
last = unicode(last_elt)
try:
count_elt = set_elt.elements(NS_RSM, 'count').next()
except StopIteration:
count = None
else:
try:
count = int(unicode(count_elt))
except ValueError:
raise RSMError("invalid count in RSM response")
return RSMResponse(first, last, index, count)
def toElement(self):
"""
Return the DOM representation of this RSM request.
@rtype: L{domish.Element}
"""
set_elt = domish.Element((NS_RSM, 'set'))
if self.first is not None:
first_elt = set_elt.addElement('first', content=self.first)
if self.index is not None:
first_elt['index'] = unicode(self.index)
set_elt.addElement('last', content=self.last)
if self.count is not None:
set_elt.addElement('count', content=unicode(self.count))
return set_elt
def render(self, element):
"""Embed the DOM representation of this RSM response in the given element.
@param element: Element to contain the RSM response.
@type element: L{domish.Element}
@return: RSM request element.
@rtype: L{domish.Element}
"""
set_elt = self.toElement()
element.addChild(set_elt)
return set_elt
def toDict(self):
"""Return a dict representation of the object.
@return: a dict of strings.
@rtype: C{dict} binding C{unicode} to C{unicode}
"""
result = {}
for attr in ('first', 'last', 'index', 'count'):
value = getattr(self, attr)
if value is not None:
result[attr] = unicode(value)
return result
class PubSubRequest(pubsub.PubSubRequest):
"""PubSubRequest extension to handle RSM.
@ivar rsm: RSM request instance.
@type rsm: L{RSMRequest}
"""
rsm = None
_parameters = copy.deepcopy(pubsub.PubSubRequest._parameters)
_parameters['items'].append('rsm')
def _parse_rsm(self, verbElement):
try:
self.rsm = RSMRequest.fromElement(verbElement.parent)
except RSMNotFoundError:
self.rsm = None
def _render_rsm(self, verbElement):
if self.rsm:
self.rsm.render(verbElement.parent)
class PubSubClient(pubsub.PubSubClient):
"""PubSubClient extension to handle RSM."""
_request_class = PubSubRequest
def items(self, service, nodeIdentifier, maxItems=None, itemIdentifiers=None,
subscriptionIdentifier=None, sender=None, rsm_request=None):
"""
Retrieve previously published items from a publish subscribe node.
@param service: The publish subscribe service that keeps the node.
@type service: L{JID<twisted.words.protocols.jabber.jid.JID>}
@param nodeIdentifier: The identifier of the node.
@type nodeIdentifier: C{unicode}
@param maxItems: Optional limit on the number of retrieved items.
@type maxItems: C{int}
@param itemIdentifiers: Identifiers of the items to be retrieved.
@type itemIdentifiers: C{set}
@param subscriptionIdentifier: Optional subscription identifier. In
case the node has been subscribed to multiple times, this narrows
the results to the specific subscription.
@type subscriptionIdentifier: C{unicode}
@param ext_data: extension data.
@type ext_data: L{dict}
@return: a Deferred that fires a C{list} of C{tuple} of L{domish.Element}, L{RSMResponse}.
@rtype: L{defer.Deferred}
"""
# XXX: we have to copy initial method instead of calling it,
# as original cb remove all non item elements
request = self._request_class('items')
request.recipient = service
request.nodeIdentifier = nodeIdentifier
if maxItems:
request.maxItems = str(int(maxItems))
request.subscriptionIdentifier = subscriptionIdentifier
request.sender = sender
request.itemIdentifiers = itemIdentifiers
request.rsm = rsm_request
def cb(iq):
items = []
pubsub_elt = iq.pubsub
if pubsub_elt.items:
for element in pubsub_elt.items.elements(pubsub.NS_PUBSUB, 'item'):
items.append(element)
try:
rsm_response = RSMResponse.fromElement(pubsub_elt)
except RSMNotFoundError:
rsm_response = None
return (items, rsm_response)
d = request.send(self.xmlstream)
d.addCallback(cb)
return d
class PubSubService(pubsub.PubSubService):
"""PubSubService extension to handle RSM."""
_request_class = PubSubRequest
def _toResponse_items(self, elts, resource, request):
# default method only manage <item/> elements
# but we need to add RSM set element
rsm_elt = None
for idx, elt in enumerate(reversed(elts)):
if elt.name == "set" and elt.uri == NS_RSM:
rsm_elt = elts.pop(-1-idx)
break
response = pubsub.PubSubService._toResponse_items(self, elts,
resource, request)
if rsm_elt is not None:
response.addChild(rsm_elt)
return response

File diff suppressed because it is too large Load diff

View file

@ -1,662 +0,0 @@
# 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
RSMResponse.__eq__ = lambda self, other: self.first == other.first and\
self.last == other.last and\
self.index == other.index and\
self.count == other.count
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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>1</max>
</set>
</query>
"""
request = RSMRequest.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>3</max>
<after>peterpan@neverland.lit</after>
</set>
</query>
"""
request = RSMRequest.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>5</max>
<before>peterpan@pixyland.org</before>
</set>
</query>
"""
request = RSMRequest.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>7</max>
<before/>
</set>
</query>
"""
request = RSMRequest.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>9</max>
<index>371</index>
</set>
</query>
"""
request = RSMRequest.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<nick>Pete</nick>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>0</max>
</set>
</query>
"""
request = RSMRequest.fromElement(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'10', element.items['max_items']) # not 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, index=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, index=127, first=u'127')
self.assertRaises(AssertionError, RSMResponse, index=127, last=u'351')
def test_parse(self):
"""
Parse a response element returning a page.
"""
xml = """
<query xmlns='jabber:iq:search'>
<set xmlns='http://jabber.org/protocol/rsm'>
<first index='20'>stpeter@jabber.org</first>
<last>peterpan@neverland.lit</last>
<count>800</count>
</set>
</query>
"""
response = RSMResponse.fromElement(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 = """
<query xmlns='jabber:iq:search'>
<set xmlns='http://jabber.org/protocol/rsm'>
<count>800</count>
</set>
</query>
"""
response = RSMResponse.fromElement(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(u'stpeter@jabber.org', u'peterpan@neverland.lit', 20, 800).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(count=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(response):
items, rsm = response
self.assertEquals(2, len(items))
self.assertEquals([item1, item2], items)
self.assertEquals(rsm, RSMResponse('item1', 'item2', 0, 800))
d = self.protocol.items(JID('pubsub.example.org'), 'test',
rsm_request=RSMRequest(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(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(u'item1', u'item2', 0, 800).render(response.pubsub)
self.stub.send(response)
return d
def test_itemsAfter(self):
"""
Test sending items request to get the next page.
"""
def cb(response):
items, rsm = response
self.assertEquals(2, len(items))
self.assertEquals([item1, item2], items)
self.assertEquals(rsm, RSMResponse('item3', 'item4', 2, 800))
d = self.protocol.items(JID('pubsub.example.org'), 'test',
rsm_request=RSMRequest(2, after=u'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(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(u'item3', u'item4', 2, 800).render(response.pubsub)
self.stub.send(response)
return d
def test_itemsBefore(self):
"""
Test sending items request to get the previous page.
"""
def cb(response):
items, rsm = response
self.assertEquals(2, len(items))
self.assertEquals([item1, item2], items)
self.assertEquals(rsm, RSMResponse('item1', 'item2', 0, 800))
d = self.protocol.items(JID('pubsub.example.org'), 'test',
rsm_request=RSMRequest(2, before=u'item3'))
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(u'item1', u'item2', 0, 800).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(response):
items, rsm = response
self.assertEquals(3, len(items))
self.assertEquals([item1, item2, item3], items)
self.assertEquals(rsm, RSMResponse('item4', 'item6', 3, 800))
d = self.protocol.items(JID('pubsub.example.org'), 'test',
rsm_request=RSMRequest(3, index=3))
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(u'item4', u'item6', 3, 800).render(response.pubsub)
self.stub.send(response)
return d
def test_itemsCount(self):
"""
Test sending items request to count them.
"""
def cb(response):
items, rsm = response
self.assertEquals(0, len(items))
self.assertEquals(rsm, RSMResponse(count=800))
d = self.protocol.items(JID('pubsub.example.org'), 'test',
rsm_request=RSMRequest(0))
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(count=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 = """
<iq type='get' to='pubsub.example.org'
from='user@example.org'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='test'/>
</pubsub>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>1</max>
</set>
</iq>
"""
def items(request):
rsm = RSMResponse(u'item', u'item', 0, 800).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 = """
<iq type='get' to='pubsub.example.org'
from='user@example.org'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='test'/>
</pubsub>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>2</max>
<index>3</index>
</set>
</iq>
"""
def items(request):
rsm = RSMResponse(u'i1', u'i2', 3, 800).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 = """
<iq type='get' to='pubsub.example.org'
from='user@example.org'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<items node='test'/>
</pubsub>
<set xmlns='http://jabber.org/protocol/rsm'>
<max>0</max>
</set>
</iq>
"""
def items(request):
rsm = RSMResponse(count=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