tmp: removed sat.tmp hierarchy and fixed references to it as it is now an independant sat_tmp repository
This commit is contained in:
parent
4809f9aa38
commit
a5f28ef7dd
16 changed files with 11 additions and 7660 deletions
5
setup.py
5
setup.py
|
|
@ -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},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
Loading…
Reference in a new issue