Merge branch 'develop' into roster

This commit is contained in:
Lance Stout 2011-03-24 13:15:00 -04:00
commit 77601f7262
9 changed files with 315 additions and 153 deletions

View file

@ -137,74 +137,6 @@ class CommandUserBot(sleekxmpp.ClientXMPP):
# handler is provided. # handler is provided.
self['xep_0050'].terminate_command(session) self['xep_0050'].terminate_command(session)
def _handle_command(self, iq, session):
"""
Respond to the intial request for a command.
Arguments:
iq -- The iq stanza containing the command request.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
form = self['xep_0004'].makeForm('form', 'Greeting')
form.addField(var='greeting',
ftype='text-single',
label='Your greeting')
session['payload'] = form
session['next'] = self._handle_command_complete
session['has_next'] = False
# Other useful session values:
# session['to'] -- The JID that received the
# command request.
# session['from'] -- The JID that sent the
# command request.
# session['has_next'] = True -- There are more steps to complete
# session['allow_complete'] = True -- Allow user to finish immediately
# and possibly skip steps
# session['cancel'] = handler -- Assign a handler for if the user
# cancels the command.
# session['notes'] = [ -- Add informative notes about the
# ('info', 'Info message'), command's results.
# ('warning', 'Warning message'),
# ('error', 'Error message')]
return session
def _handle_command_complete(self, payload, session):
"""
Process a command result from the user.
Arguments:
payload -- Either a single item, such as a form, or a list
of items or forms if more than one form was
provided to the user. The payload may be any
stanza, such as jabber:x:oob for out of band
data, or jabber:x:data for typical data forms.
session -- A dictionary of data relevant to the command
session. Additional, custom data may be saved
here to persist across handler callbacks.
"""
# In this case (as is typical), the payload is a form
form = payload
greeting = form['values']['greeting']
self.send_message(mto=session['from'],
mbody="%s, World!" % greeting)
# Having no return statement is the same as unsetting the 'payload'
# and 'next' session values and returning the session.
# Unless it is the final step, always return the session dictionary.
session['payload'] = None
session['next'] = None
return session
if __name__ == '__main__': if __name__ == '__main__':
# Setup the command line arguments. # Setup the command line arguments.

View file

@ -52,6 +52,7 @@ packages = [ 'sleekxmpp',
'sleekxmpp/plugins/xep_0050', 'sleekxmpp/plugins/xep_0050',
'sleekxmpp/plugins/xep_0059', 'sleekxmpp/plugins/xep_0059',
'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0085',
'sleekxmpp/plugins/xep_0086',
'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0092',
'sleekxmpp/plugins/xep_0128', 'sleekxmpp/plugins/xep_0128',
'sleekxmpp/plugins/xep_0199', 'sleekxmpp/plugins/xep_0199',

View file

@ -1,49 +0,0 @@
from __future__ import with_statement
from . import base
import logging
from xml.etree import cElementTree as ET
import copy
class xep_0086(base.base_plugin):
"""
XEP-0086 Error Condition Mappings
"""
def plugin_init(self):
self.xep = '0086'
self.description = 'Error Condition Mappings'
self.error_map = {
'bad-request':('modify','400'),
'conflict':('cancel','409'),
'feature-not-implemented':('cancel','501'),
'forbidden':('auth','403'),
'gone':('modify','302'),
'internal-server-error':('wait','500'),
'item-not-found':('cancel','404'),
'jid-malformed':('modify','400'),
'not-acceptable':('modify','406'),
'not-allowed':('cancel','405'),
'not-authorized':('auth','401'),
'payment-required':('auth','402'),
'recipient-unavailable':('wait','404'),
'redirect':('modify','302'),
'registration-required':('auth','407'),
'remote-server-not-found':('cancel','404'),
'remote-server-timeout':('wait','504'),
'resource-constraint':('wait','500'),
'service-unavailable':('cancel','503'),
'subscription-required':('auth','407'),
'undefined-condition':(None,'500'),
'unexpected-request':('wait','400')
}
def makeError(self, condition, cdata=None, errorType=None, text=None, customElem=None):
conditionElem = self.xmpp.makeStanzaErrorCondition(condition, cdata)
if errorType is None:
error = self.xmpp.makeStanzaError(conditionElem, self.error_map[condition][0], self.error_map[condition][1], text, customElem)
else:
error = self.xmpp.makeStanzaError(conditionElem, errorType, self.error_map[condition][1], text, customElem)
error.append(conditionElem)
return error

View file

@ -0,0 +1,10 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.plugins.xep_0086.stanza import LegacyError
from sleekxmpp.plugins.xep_0086.legacy_error import xep_0086

View file

@ -0,0 +1,42 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream import register_stanza_plugin
from sleekxmpp.plugins.base import base_plugin
from sleekxmpp.plugins.xep_0086 import stanza, LegacyError
class xep_0086(base_plugin):
"""
XEP-0086: Error Condition Mappings
Older XMPP implementations used code based error messages, similar
to HTTP response codes. Since then, error condition elements have
been introduced. XEP-0086 provides a mapping between the new
condition elements and a combination of error types and the older
response codes.
Also see <http://xmpp.org/extensions/xep-0086.html>.
Configuration Values:
override -- Indicates if applying legacy error codes should
be done automatically. Defaults to True.
If False, then inserting legacy error codes can
be done using:
iq['error']['legacy']['condition'] = ...
"""
def plugin_init(self):
self.xep = '0086'
self.description = 'Error Condition Mappings'
self.stanza = stanza
register_stanza_plugin(Error, LegacyError,
overrides=self.config.get('override', True))

View file

@ -0,0 +1,91 @@
"""
SleekXMPP: The Sleek XMPP Library
Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout
This file is part of SleekXMPP.
See the file LICENSE for copying permission.
"""
from sleekxmpp.stanza import Error
from sleekxmpp.xmlstream import ElementBase, ET, register_stanza_plugin
class LegacyError(ElementBase):
"""
Older XMPP implementations used code based error messages, similar
to HTTP response codes. Since then, error condition elements have
been introduced. XEP-0086 provides a mapping between the new
condition elements and a combination of error types and the older
response codes.
Also see <http://xmpp.org/extensions/xep-0086.html>.
Example legacy error stanzas:
<error xmlns="jabber:client" code="501" type="cancel">
<feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
<error code="402" type="auth">
<payment-required
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
</error>
Attributes:
error_map -- A map of error conditions to error types and
code values.
Methods:
setup -- Overrides ElementBase.setup
set_condition -- Remap the type and code interfaces when a
condition is set.
"""
name = 'legacy'
namespace = Error.namespace
plugin_attrib = name
interfaces = set(('condition',))
overrides = ['set_condition']
error_map = {'bad-request': ('modify','400'),
'conflict': ('cancel','409'),
'feature-not-implemented': ('cancel','501'),
'forbidden': ('auth','403'),
'gone': ('modify','302'),
'internal-server-error': ('wait','500'),
'item-not-found': ('cancel','404'),
'jid-malformed': ('modify','400'),
'not-acceptable': ('modify','406'),
'not-allowed': ('cancel','405'),
'not-authorized': ('auth','401'),
'payment-required': ('auth','402'),
'recipient-unavailable': ('wait','404'),
'redirect': ('modify','302'),
'registration-required': ('auth','407'),
'remote-server-not-found': ('cancel','404'),
'remote-server-timeout': ('wait','504'),
'resource-constraint': ('wait','500'),
'service-unavailable': ('cancel','503'),
'subscription-required': ('auth','407'),
'undefined-condition': (None,'500'),
'unexpected-request': ('wait','400')}
def setup(self, xml):
"""Don't create XML for the plugin."""
self.xml = ET.Element('')
def set_condition(self, value):
"""
Set the error type and code based on the given error
condition value.
Arguments:
value -- The new error condition.
"""
self.parent().set_condition(value)
error_data = self.error_map.get(value, None)
if error_data is not None:
if error_data[0] is not None:
self.parent()['type'] = error_data[0]
self.parent()['code'] = error_data[1]

View file

@ -24,24 +24,32 @@ log = logging.getLogger(__name__)
XML_TYPE = type(ET.Element('xml')) XML_TYPE = type(ET.Element('xml'))
def register_stanza_plugin(stanza, plugin, iterable=False): def register_stanza_plugin(stanza, plugin, iterable=False, overrides=False):
""" """
Associate a stanza object as a plugin for another stanza. Associate a stanza object as a plugin for another stanza.
Arguments: Arguments:
stanza -- The class of the parent stanza. stanza -- The class of the parent stanza.
plugin -- The class of the plugin stanza. plugin -- The class of the plugin stanza.
iterable -- Indicates if the plugin stanza iterable -- Indicates if the plugin stanza should be
should be included in the parent included in the parent stanza's iterable
stanza's iterable 'substanzas' 'substanzas' interface results.
interface results. overrides -- Indicates if the plugin should be allowed
to override the interface handlers for
the parent stanza.
""" """
tag = "{%s}%s" % (plugin.namespace, plugin.name) tag = "{%s}%s" % (plugin.namespace, plugin.name)
stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
stanza.plugin_tag_map[tag] = plugin stanza.plugin_tag_map[tag] = plugin
if iterable: if iterable:
# Prevent weird memory reference gotchas.
stanza.plugin_iterables = stanza.plugin_iterables.copy() stanza.plugin_iterables = stanza.plugin_iterables.copy()
stanza.plugin_iterables.add(plugin) stanza.plugin_iterables.add(plugin)
if overrides:
# Prevent weird memory reference gotchas.
stanza.plugin_overrides = stanza.plugin_overrides.copy()
for interface in plugin.overrides:
stanza.plugin_overrides[interface] = plugin.plugin_attrib
# To maintain backwards compatibility for now, preserve the camel case name. # To maintain backwards compatibility for now, preserve the camel case name.
@ -130,6 +138,11 @@ class ElementBase(object):
subitem -- A set of stanza classes which are allowed to subitem -- A set of stanza classes which are allowed to
be added as substanzas. Deprecated version be added as substanzas. Deprecated version
of plugin_iterables. of plugin_iterables.
overrides -- A list of interfaces prepended with 'get_',
'set_', or 'del_'. If the stanza is registered
as a plugin with overrides=True, then the
parent's interface handlers will be
overridden by the plugin's matching handler.
types -- A set of generic type attribute values. types -- A set of generic type attribute values.
tag -- The namespaced name of the stanza's root tag -- The namespaced name of the stanza's root
element. Example: "{foo_ns}bar" element. Example: "{foo_ns}bar"
@ -139,6 +152,10 @@ class ElementBase(object):
associated plugin stanza classes. associated plugin stanza classes.
plugin_iterables -- A set of stanza classes which are allowed to plugin_iterables -- A set of stanza classes which are allowed to
be added as substanzas. be added as substanzas.
plugin_overrides -- A mapping of interfaces prepended with 'get_',
'set_' or 'del_' to plugin attrib names. Allows
a plugin to override the behaviour of a parent
stanza's interface handlers.
plugin_tag_map -- A mapping of plugin stanza tag names with plugin_tag_map -- A mapping of plugin stanza tag names with
the associated plugin stanza classes. the associated plugin stanza classes.
is_extension -- When True, allows the stanza to provide one is_extension -- When True, allows the stanza to provide one
@ -204,7 +221,9 @@ class ElementBase(object):
interfaces = set(('type', 'to', 'from', 'id', 'payload')) interfaces = set(('type', 'to', 'from', 'id', 'payload'))
types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat')) types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
sub_interfaces = tuple() sub_interfaces = tuple()
overrides = {}
plugin_attrib_map = {} plugin_attrib_map = {}
plugin_overrides = {}
plugin_iterables = set() plugin_iterables = set()
plugin_tag_map = {} plugin_tag_map = {}
subitem = set() subitem = set()
@ -380,12 +399,13 @@ class ElementBase(object):
The search order for interface value retrieval for an interface The search order for interface value retrieval for an interface
named 'foo' is: named 'foo' is:
1. The list of substanzas. 1. The list of substanzas.
2. The result of calling get_foo. 2. The result of calling the get_foo override handler.
3. The result of calling getFoo. 3. The result of calling get_foo.
4. The contents of the foo subelement, if foo is a sub interface. 4. The result of calling getFoo.
5. The value of the foo attribute of the XML object. 5. The contents of the foo subelement, if foo is a sub interface.
6. The plugin named 'foo' 6. The value of the foo attribute of the XML object.
7. An empty string. 7. The plugin named 'foo'
8. An empty string.
Arguments: Arguments:
attrib -- The name of the requested stanza interface. attrib -- The name of the requested stanza interface.
@ -395,6 +415,16 @@ class ElementBase(object):
elif attrib in self.interfaces: elif attrib in self.interfaces:
get_method = "get_%s" % attrib.lower() get_method = "get_%s" % attrib.lower()
get_method2 = "get%s" % attrib.title() get_method2 = "get%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(get_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], get_method, None)
if handler:
return handler()
if hasattr(self, get_method): if hasattr(self, get_method):
return getattr(self, get_method)() return getattr(self, get_method)()
elif hasattr(self, get_method2): elif hasattr(self, get_method2):
@ -429,13 +459,14 @@ class ElementBase(object):
The effect of interface value assignment for an interface The effect of interface value assignment for an interface
named 'foo' will be one of: named 'foo' will be one of:
1. Delete the interface's contents if the value is None. 1. Delete the interface's contents if the value is None.
2. Call set_foo, if it exists. 2. Call the set_foo override handler, if it exists.
3. Call setFoo, if it exists. 3. Call set_foo, if it exists.
4. Set the text of a foo element, if foo is in sub_interfaces. 4. Call setFoo, if it exists.
5. Set the value of a top level XML attribute name foo. 5. Set the text of a foo element, if foo is in sub_interfaces.
6. Attempt to pass value to a plugin named foo using the plugin's 6. Set the value of a top level XML attribute name foo.
7. Attempt to pass value to a plugin named foo using the plugin's
foo interface. foo interface.
7. Do nothing. 8. Do nothing.
Arguments: Arguments:
attrib -- The name of the stanza interface to modify. attrib -- The name of the stanza interface to modify.
@ -445,6 +476,16 @@ class ElementBase(object):
if value is not None: if value is not None:
set_method = "set_%s" % attrib.lower() set_method = "set_%s" % attrib.lower()
set_method2 = "set%s" % attrib.title() set_method2 = "set%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(set_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], set_method, None)
if handler:
return handler(value)
if hasattr(self, set_method): if hasattr(self, set_method):
getattr(self, set_method)(value,) getattr(self, set_method)(value,)
elif hasattr(self, set_method2): elif hasattr(self, set_method2):
@ -480,12 +521,13 @@ class ElementBase(object):
The effect of deleting a stanza interface value named foo will be The effect of deleting a stanza interface value named foo will be
one of: one of:
1. Call del_foo, if it exists. 1. Call del_foo override handler, if it exists.
2. Call delFoo, if it exists. 2. Call del_foo, if it exists.
3. Delete foo element, if foo is in sub_interfaces. 3. Call delFoo, if it exists.
4. Delete top level XML attribute named foo. 4. Delete foo element, if foo is in sub_interfaces.
5. Remove the foo plugin, if it was loaded. 5. Delete top level XML attribute named foo.
6. Do nothing. 6. Remove the foo plugin, if it was loaded.
7. Do nothing.
Arguments: Arguments:
attrib -- The name of the affected stanza interface. attrib -- The name of the affected stanza interface.
@ -493,6 +535,16 @@ class ElementBase(object):
if attrib in self.interfaces: if attrib in self.interfaces:
del_method = "del_%s" % attrib.lower() del_method = "del_%s" % attrib.lower()
del_method2 = "del%s" % attrib.title() del_method2 = "del%s" % attrib.title()
if self.plugin_overrides:
plugin = self.plugin_overrides.get(del_method, None)
if plugin:
if plugin not in self.plugins:
self.init_plugin(plugin)
handler = getattr(self.plugins[plugin], del_method, None)
if handler:
return handler()
if hasattr(self, del_method): if hasattr(self, del_method):
getattr(self, del_method)() getattr(self, del_method)()
elif hasattr(self, del_method2): elif hasattr(self, del_method2):

View file

@ -53,9 +53,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin, iterable=True)
stanza = TestStanza() stanza = TestStanza()
stanza['bar'] = 'a' stanza['bar'] = 'a'
@ -100,8 +99,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = set((TestSubStanza,))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
register_stanza_plugin(TestStanza, TestStanzaPlugin2) register_stanza_plugin(TestStanza, TestStanzaPlugin2)
@ -115,7 +114,7 @@ class TestElementBase(SleekTest):
'substanzas': [{'__childtag__': '{foo}subfoo', 'substanzas': [{'__childtag__': '{foo}subfoo',
'bar': 'c', 'bar': 'c',
'baz': ''}]} 'baz': ''}]}
stanza.setStanzaValues(values) stanza.values = values
self.check(stanza, """ self.check(stanza, """
<foo xmlns="foo" bar="a"> <foo xmlns="foo" bar="a">
@ -143,7 +142,7 @@ class TestElementBase(SleekTest):
plugin_attrib = "foobar" plugin_attrib = "foobar"
interfaces = set(('fizz',)) interfaces = set(('fizz',))
TestStanza.subitem = (TestStanza,) register_stanza_plugin(TestStanza, TestStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza() stanza = TestStanza()
@ -457,7 +456,6 @@ class TestElementBase(SleekTest):
namespace = "foo" namespace = "foo"
interfaces = set(('bar','baz', 'qux')) interfaces = set(('bar','baz', 'qux'))
sub_interfaces = set(('qux',)) sub_interfaces = set(('qux',))
subitem = (TestSubStanza,)
def setQux(self, value): def setQux(self, value):
self._set_sub_text('qux', text=value) self._set_sub_text('qux', text=value)
@ -470,6 +468,7 @@ class TestElementBase(SleekTest):
namespace = "http://test/slash/bar" namespace = "http://test/slash/bar"
interfaces = set(('attrib',)) interfaces = set(('attrib',))
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
register_stanza_plugin(TestStanza, TestStanzaPlugin) register_stanza_plugin(TestStanza, TestStanzaPlugin)
stanza = TestStanza() stanza = TestStanza()
@ -590,7 +589,8 @@ class TestElementBase(SleekTest):
name = "foo" name = "foo"
namespace = "foo" namespace = "foo"
interfaces = set(('bar', 'baz')) interfaces = set(('bar', 'baz'))
subitem = (TestSubStanza,)
register_stanza_plugin(TestStanza, TestSubStanza, iterable=True)
stanza = TestStanza() stanza = TestStanza()
substanza1 = TestSubStanza() substanza1 = TestSubStanza()
@ -657,4 +657,87 @@ class TestElementBase(SleekTest):
self.failUnless(stanza1 != stanza2, self.failUnless(stanza1 != stanza2,
"Divergent stanza copies incorrectly compared equal.") "Divergent stanza copies incorrectly compared equal.")
def testExtension(self):
"""Testing using is_extension."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestExtension(ElementBase):
name = 'extended'
namespace = 'foo'
plugin_attrib = name
interfaces = set((name,))
is_extension = True
def set_extended(self, value):
self.xml.text = value
def get_extended(self):
return self.xml.text
def del_extended(self):
self.parent().xml.remove(self.xml)
register_stanza_plugin(TestStanza, TestExtension)
stanza = TestStanza()
stanza['extended'] = 'testing'
self.check(stanza, """
<foo xmlns="foo">
<extended>testing</extended>
</foo>
""")
self.failUnless(stanza['extended'] == 'testing',
"Could not retrieve stanza extension value.")
del stanza['extended']
self.check(stanza, """
<foo xmlns="foo" />
""")
def testOverrides(self):
"""Test using interface overrides."""
class TestStanza(ElementBase):
name = "foo"
namespace = "foo"
interfaces = set(('bar', 'baz'))
class TestOverride(ElementBase):
name = 'overrider'
namespace = 'foo'
plugin_attrib = name
interfaces = set(('bar',))
overrides = ['set_bar']
def setup(self, xml):
# Don't create XML for the plugin
self.xml = ET.Element('')
def set_bar(self, value):
if not value.startswith('override-'):
self.parent()._set_attr('bar', 'override-%s' % value)
else:
self.parent()._set_attr('bar', value)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
<foo xmlns="foo" bar="foo" />
""")
register_stanza_plugin(TestStanza, TestOverride, overrides=True)
stanza = TestStanza()
stanza['bar'] = 'foo'
self.check(stanza, """
<foo xmlns="foo" bar="override-foo" />
""")
suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase) suite = unittest.TestLoader().loadTestsFromTestCase(TestElementBase)

View file

@ -37,7 +37,7 @@ class TestStreamExceptions(SleekTest):
self.send(""" self.send("""
<message type="error"> <message type="error">
<error type="cancel"> <error type="cancel" code="501">
<feature-not-implemented <feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
@ -73,7 +73,7 @@ class TestStreamExceptions(SleekTest):
self.send(""" self.send("""
<iq type="error" id="0"> <iq type="error" id="0">
<query xmlns="test" /> <query xmlns="test" />
<error type="cancel"> <error type="cancel" code="501">
<feature-not-implemented <feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
@ -103,7 +103,7 @@ class TestStreamExceptions(SleekTest):
self.send(""" self.send("""
<message type="error"> <message type="error">
<error type="cancel"> <error type="cancel" code="501">
<feature-not-implemented <feature-not-implemented
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
@ -137,7 +137,7 @@ class TestStreamExceptions(SleekTest):
self.send(""" self.send("""
<message type="error"> <message type="error">
<error type="cancel"> <error type="cancel" code="500">
<undefined-condition <undefined-condition
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" /> xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">