Compare commits
15 Commits
1.0-Beta1
...
sleek-1.0-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bdcd9ef9d | ||
|
|
2eff35cc7a | ||
|
|
ac330b5c6c | ||
|
|
46ffa8e9fe | ||
|
|
03847497cc | ||
|
|
185d7cf28e | ||
|
|
8aa3d0c047 | ||
|
|
9e3d506651 | ||
|
|
2f3ff37a24 | ||
|
|
1f09d60a52 | ||
|
|
d528884723 | ||
|
|
d9aff3d36f | ||
|
|
04cc48775d | ||
|
|
27ebb6e8f6 | ||
|
|
8f55704928 |
@@ -553,6 +553,7 @@ class BaseXMPP(XMLStream):
|
|||||||
priority = presence['priority']
|
priority = presence['priority']
|
||||||
|
|
||||||
was_offline = False
|
was_offline = False
|
||||||
|
got_online = False
|
||||||
old_roster = self.roster.get(jid, {}).get(resource, {})
|
old_roster = self.roster.get(jid, {}).get(resource, {})
|
||||||
|
|
||||||
# Create a new roster entry if needed.
|
# Create a new roster entry if needed.
|
||||||
@@ -569,7 +570,7 @@ class BaseXMPP(XMLStream):
|
|||||||
# Determine if the user has just come online.
|
# Determine if the user has just come online.
|
||||||
if not resource in connections:
|
if not resource in connections:
|
||||||
if show == 'available' or show in presence.showtypes:
|
if show == 'available' or show in presence.showtypes:
|
||||||
self.event("got_online", presence)
|
got_online = True
|
||||||
was_offline = True
|
was_offline = True
|
||||||
connections[resource] = {}
|
connections[resource] = {}
|
||||||
|
|
||||||
@@ -601,6 +602,8 @@ class BaseXMPP(XMLStream):
|
|||||||
|
|
||||||
# Presence state has changed.
|
# Presence state has changed.
|
||||||
self.event("changed_status", presence)
|
self.event("changed_status", presence)
|
||||||
|
if got_online:
|
||||||
|
self.event("got_online", presence)
|
||||||
logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
|
logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource,
|
||||||
show, status))
|
show, status))
|
||||||
|
|
||||||
@@ -618,8 +621,8 @@ class BaseXMPP(XMLStream):
|
|||||||
None * Disable automatic handling and use
|
None * Disable automatic handling and use
|
||||||
a custom handler.
|
a custom handler.
|
||||||
"""
|
"""
|
||||||
presence = self.Presence()
|
presence.reply()
|
||||||
presence['to'] = presence['from'].bare
|
presence['to'] = presence['to'].bare
|
||||||
|
|
||||||
# We are using trinary logic, so conditions have to be
|
# We are using trinary logic, so conditions have to be
|
||||||
# more explicit than usual.
|
# more explicit than usual.
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ class ClientXMPP(BaseXMPP):
|
|||||||
iq['roster']['items'] = {jid: {'name': name,
|
iq['roster']['items'] = {jid: {'name': name,
|
||||||
'subscription': subscription,
|
'subscription': subscription,
|
||||||
'groups': groups}}
|
'groups': groups}}
|
||||||
resp = iq.send()
|
response = iq.send()
|
||||||
return resp['type'] == 'result'
|
return response['type'] == 'result'
|
||||||
|
|
||||||
def del_roster_item(self, jid):
|
def del_roster_item(self, jid):
|
||||||
"""
|
"""
|
||||||
@@ -235,8 +235,8 @@ class ClientXMPP(BaseXMPP):
|
|||||||
def get_roster(self):
|
def get_roster(self):
|
||||||
"""Request the roster from the server."""
|
"""Request the roster from the server."""
|
||||||
iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster')
|
iq = self.Iq()._set_stanza_values({'type': 'get'}).enable('roster')
|
||||||
iq.send()
|
response = iq.send()
|
||||||
self._handle_roster(iq, request=True)
|
self._handle_roster(response, request=True)
|
||||||
|
|
||||||
def _handle_stream_features(self, features):
|
def _handle_stream_features(self, features):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ class ComponentXMPP(BaseXMPP):
|
|||||||
self.server_port = port
|
self.server_port = port
|
||||||
self.set_jid(jid)
|
self.set_jid(jid)
|
||||||
self.secret = secret
|
self.secret = secret
|
||||||
|
self.plugin_config = plugin_config
|
||||||
|
self.plugin_whitelist = plugin_whitelist
|
||||||
self.is_component = True
|
self.is_component = True
|
||||||
|
|
||||||
self.register_handler(
|
self.register_handler(
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ class XMPPError(Exception):
|
|||||||
element. Same as the additional arguments to
|
element. Same as the additional arguments to
|
||||||
the ET.Element constructor.
|
the ET.Element constructor.
|
||||||
"""
|
"""
|
||||||
|
if extension_args is None:
|
||||||
|
extension_args = {}
|
||||||
|
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
self.text = text
|
self.text = text
|
||||||
self.etype = etype
|
self.etype = etype
|
||||||
|
|||||||
@@ -92,6 +92,12 @@ class Presence(RootStanza):
|
|||||||
|
|
||||||
return StanzaBase.setup(self, xml)
|
return StanzaBase.setup(self, xml)
|
||||||
|
|
||||||
|
def exception(self, e):
|
||||||
|
"""
|
||||||
|
Override exception passback for presence.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def set_show(self, show):
|
def set_show(self, show):
|
||||||
"""
|
"""
|
||||||
Set the value of the <show> element.
|
Set the value of the <show> element.
|
||||||
|
|||||||
@@ -103,6 +103,41 @@ class SleekTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
return Presence(None, *args, **kwargs)
|
return Presence(None, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def check_JID(self, jid, user=None, domain=None, resource=None,
|
||||||
|
bare=None, full=None, string=None):
|
||||||
|
"""
|
||||||
|
Verify the components of a JID.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
jid -- The JID object to test.
|
||||||
|
user -- Optional. The user name portion of the JID.
|
||||||
|
domain -- Optional. The domain name portion of the JID.
|
||||||
|
resource -- Optional. The resource portion of the JID.
|
||||||
|
bare -- Optional. The bare JID.
|
||||||
|
full -- Optional. The full JID.
|
||||||
|
string -- Optional. The string version of the JID.
|
||||||
|
"""
|
||||||
|
if user is not None:
|
||||||
|
self.assertEqual(jid.user, user,
|
||||||
|
"User does not match: %s" % jid.user)
|
||||||
|
if domain is not None:
|
||||||
|
self.assertEqual(jid.domain, domain,
|
||||||
|
"Domain does not match: %s" % jid.domain)
|
||||||
|
if resource is not None:
|
||||||
|
self.assertEqual(jid.resource, resource,
|
||||||
|
"Resource does not match: %s" % jid.resource)
|
||||||
|
if bare is not None:
|
||||||
|
self.assertEqual(jid.bare, bare,
|
||||||
|
"Bare JID does not match: %s" % jid.bare)
|
||||||
|
if full is not None:
|
||||||
|
self.assertEqual(jid.full, full,
|
||||||
|
"Full JID does not match: %s" % jid.full)
|
||||||
|
if string is not None:
|
||||||
|
self.assertEqual(str(jid), string,
|
||||||
|
"String does not match: %s" % str(jid))
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Methods for comparing stanza objects to XML strings
|
# Methods for comparing stanza objects to XML strings
|
||||||
|
|
||||||
@@ -294,6 +329,7 @@ class SleekTest(unittest.TestCase):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Unknown socket type.")
|
raise ValueError("Unknown socket type.")
|
||||||
|
|
||||||
|
self.xmpp.register_plugins()
|
||||||
self.xmpp.process(threaded=True)
|
self.xmpp.process(threaded=True)
|
||||||
if skip:
|
if skip:
|
||||||
# Clear startup stanzas
|
# Clear startup stanzas
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class JID(object):
|
|||||||
full, or bare.
|
full, or bare.
|
||||||
"""
|
"""
|
||||||
if name == 'resource':
|
if name == 'resource':
|
||||||
if self._resource is None:
|
if self._resource is None and '/' in self._jid:
|
||||||
self._resource = self._jid.split('/', 1)[-1]
|
self._resource = self._jid.split('/', 1)[-1]
|
||||||
return self._resource or ""
|
return self._resource or ""
|
||||||
elif name == 'user':
|
elif name == 'user':
|
||||||
@@ -94,21 +94,15 @@ class JID(object):
|
|||||||
elif name in ('server', 'domain', 'host'):
|
elif name in ('server', 'domain', 'host'):
|
||||||
self.domain = value
|
self.domain = value
|
||||||
elif name in ('full', 'jid'):
|
elif name in ('full', 'jid'):
|
||||||
if '@' not in value:
|
|
||||||
if '/' in value:
|
|
||||||
d, r = value.split('/', 1)
|
|
||||||
object.__setattr__(self, "_resource", r)
|
|
||||||
else:
|
|
||||||
d = value
|
|
||||||
object.__setattr__(self, "_domain", d)
|
|
||||||
else:
|
|
||||||
self.reset(value)
|
self.reset(value)
|
||||||
|
self.regenerate()
|
||||||
elif name == 'bare':
|
elif name == 'bare':
|
||||||
if '@' in value:
|
if '@' in value:
|
||||||
u, d = value.split('@', 1)
|
u, d = value.split('@', 1)
|
||||||
object.__setattr__(self, "_user", u)
|
object.__setattr__(self, "_user", u)
|
||||||
object.__setattr__(self, "_domain", d)
|
object.__setattr__(self, "_domain", d)
|
||||||
else:
|
else:
|
||||||
|
object.__setattr__(self, "_user", '')
|
||||||
object.__setattr__(self, "_domain", value)
|
object.__setattr__(self, "_domain", value)
|
||||||
self.regenerate()
|
self.regenerate()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -68,9 +68,6 @@ def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
|
|||||||
for child in xml.getchildren():
|
for child in xml.getchildren():
|
||||||
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
|
output.append(tostring(child, tag_xmlns, stanza_ns, stream))
|
||||||
output.append(u"</%s>" % tag_name)
|
output.append(u"</%s>" % tag_name)
|
||||||
if xml.tail:
|
|
||||||
# If there is additional text after the element.
|
|
||||||
output.append(xml_escape(xml.tail))
|
|
||||||
elif xml.text:
|
elif xml.text:
|
||||||
# If we only have text content.
|
# If we only have text content.
|
||||||
output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name))
|
output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name))
|
||||||
|
|||||||
@@ -786,6 +786,23 @@ class XMLStream(object):
|
|||||||
if unhandled:
|
if unhandled:
|
||||||
stanza.unhandled()
|
stanza.unhandled()
|
||||||
|
|
||||||
|
def _threaded_event_wrapper(self, func, args):
|
||||||
|
"""
|
||||||
|
Capture exceptions for event handlers that run
|
||||||
|
in individual threads.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
func -- The event handler to execute.
|
||||||
|
args -- Arguments to the event handler.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
func(*args)
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = 'Error processing event handler: %s'
|
||||||
|
logging.exception(error_msg % str(func))
|
||||||
|
if hasattr(args[0], 'exception'):
|
||||||
|
args[0].exception(e)
|
||||||
|
|
||||||
def _event_runner(self):
|
def _event_runner(self):
|
||||||
"""
|
"""
|
||||||
Process the event queue and execute handlers.
|
Process the event queue and execute handlers.
|
||||||
@@ -825,14 +842,18 @@ class XMLStream(object):
|
|||||||
func, threaded, disposable = handler
|
func, threaded, disposable = handler
|
||||||
try:
|
try:
|
||||||
if threaded:
|
if threaded:
|
||||||
x = threading.Thread(name="Event_%s" % str(func),
|
x = threading.Thread(
|
||||||
target=func,
|
name="Event_%s" % str(func),
|
||||||
args=args)
|
target=self._threaded_event_wrapper,
|
||||||
|
args=(func, args))
|
||||||
x.start()
|
x.start()
|
||||||
else:
|
else:
|
||||||
func(*args)
|
func(*args)
|
||||||
except:
|
except Exception as e:
|
||||||
logging.exception('Error processing event handler: %s')
|
error_msg = 'Error processing event handler: %s'
|
||||||
|
logging.exception(error_msg % str(func))
|
||||||
|
if hasattr(args[0], 'exception'):
|
||||||
|
args[0].exception(e)
|
||||||
elif etype == 'quit':
|
elif etype == 'quit':
|
||||||
logging.debug("Quitting event runner thread")
|
logging.debug("Quitting event runner thread")
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -3,26 +3,126 @@ from sleekxmpp.xmlstream.jid import JID
|
|||||||
|
|
||||||
|
|
||||||
class TestJIDClass(SleekTest):
|
class TestJIDClass(SleekTest):
|
||||||
def testJIDfromfull(self):
|
|
||||||
j = JID('user@someserver/some/resource')
|
"""Verify that the JID class can parse and manipulate JIDs."""
|
||||||
self.assertEqual(j.user, 'user', "User does not match")
|
|
||||||
self.assertEqual(j.domain, 'someserver', "Domain does not match")
|
def testJIDFromFull(self):
|
||||||
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
|
"""Test using JID of the form 'user@server/resource/with/slashes'."""
|
||||||
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
|
self.check_JID(JID('user@someserver/some/resource'),
|
||||||
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
|
'user',
|
||||||
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
|
'someserver',
|
||||||
|
'some/resource',
|
||||||
|
'user@someserver',
|
||||||
|
'user@someserver/some/resource',
|
||||||
|
'user@someserver/some/resource')
|
||||||
|
|
||||||
def testJIDchange(self):
|
def testJIDchange(self):
|
||||||
|
"""Test changing JID of the form 'user@server/resource/with/slashes'"""
|
||||||
j = JID('user1@someserver1/some1/resource1')
|
j = JID('user1@someserver1/some1/resource1')
|
||||||
j.user = 'user'
|
j.user = 'user'
|
||||||
j.domain = 'someserver'
|
j.domain = 'someserver'
|
||||||
j.resource = 'some/resource'
|
j.resource = 'some/resource'
|
||||||
self.assertEqual(j.user, 'user', "User does not match")
|
self.check_JID(j,
|
||||||
self.assertEqual(j.domain, 'someserver', "Domain does not match")
|
'user',
|
||||||
self.assertEqual(j.resource, 'some/resource', "Resource does not match")
|
'someserver',
|
||||||
self.assertEqual(j.bare, 'user@someserver', "Bare does not match")
|
'some/resource',
|
||||||
self.assertEqual(j.full, 'user@someserver/some/resource', "Full does not match")
|
'user@someserver',
|
||||||
self.assertEqual(str(j), 'user@someserver/some/resource', "String does not match")
|
'user@someserver/some/resource',
|
||||||
|
'user@someserver/some/resource')
|
||||||
|
|
||||||
|
def testJIDaliases(self):
|
||||||
|
"""Test changing JID using aliases for domain."""
|
||||||
|
j = JID('user@someserver/resource')
|
||||||
|
j.server = 'anotherserver'
|
||||||
|
self.check_JID(j, domain='anotherserver')
|
||||||
|
j.host = 'yetanother'
|
||||||
|
self.check_JID(j, domain='yetanother')
|
||||||
|
|
||||||
|
def testJIDSetFullWithUser(self):
|
||||||
|
"""Test setting the full JID with a user portion."""
|
||||||
|
j = JID('user@domain/resource')
|
||||||
|
j.full = 'otheruser@otherdomain/otherresource'
|
||||||
|
self.check_JID(j,
|
||||||
|
'otheruser',
|
||||||
|
'otherdomain',
|
||||||
|
'otherresource',
|
||||||
|
'otheruser@otherdomain',
|
||||||
|
'otheruser@otherdomain/otherresource',
|
||||||
|
'otheruser@otherdomain/otherresource')
|
||||||
|
|
||||||
|
def testJIDFullNoUserWithResource(self):
|
||||||
|
"""
|
||||||
|
Test setting the full JID without a user
|
||||||
|
portion and with a resource.
|
||||||
|
"""
|
||||||
|
j = JID('user@domain/resource')
|
||||||
|
j.full = 'otherdomain/otherresource'
|
||||||
|
self.check_JID(j,
|
||||||
|
'',
|
||||||
|
'otherdomain',
|
||||||
|
'otherresource',
|
||||||
|
'otherdomain',
|
||||||
|
'otherdomain/otherresource',
|
||||||
|
'otherdomain/otherresource')
|
||||||
|
|
||||||
|
def testJIDFullNoUserNoResource(self):
|
||||||
|
"""
|
||||||
|
Test setting the full JID without a user
|
||||||
|
portion and without a resource.
|
||||||
|
"""
|
||||||
|
j = JID('user@domain/resource')
|
||||||
|
j.full = 'otherdomain'
|
||||||
|
self.check_JID(j,
|
||||||
|
'',
|
||||||
|
'otherdomain',
|
||||||
|
'',
|
||||||
|
'otherdomain',
|
||||||
|
'otherdomain',
|
||||||
|
'otherdomain')
|
||||||
|
|
||||||
|
def testJIDBareUser(self):
|
||||||
|
"""Test setting the bare JID with a user."""
|
||||||
|
j = JID('user@domain/resource')
|
||||||
|
j.bare = 'otheruser@otherdomain'
|
||||||
|
self.check_JID(j,
|
||||||
|
'otheruser',
|
||||||
|
'otherdomain',
|
||||||
|
'resource',
|
||||||
|
'otheruser@otherdomain',
|
||||||
|
'otheruser@otherdomain/resource',
|
||||||
|
'otheruser@otherdomain/resource')
|
||||||
|
|
||||||
|
def testJIDBareNoUser(self):
|
||||||
|
"""Test setting the bare JID without a user."""
|
||||||
|
j = JID('user@domain/resource')
|
||||||
|
j.bare = 'otherdomain'
|
||||||
|
self.check_JID(j,
|
||||||
|
'',
|
||||||
|
'otherdomain',
|
||||||
|
'resource',
|
||||||
|
'otherdomain',
|
||||||
|
'otherdomain/resource',
|
||||||
|
'otherdomain/resource')
|
||||||
|
|
||||||
|
def testJIDNoResource(self):
|
||||||
|
"""Test using JID of the form 'user@domain'."""
|
||||||
|
self.check_JID(JID('user@someserver'),
|
||||||
|
'user',
|
||||||
|
'someserver',
|
||||||
|
'',
|
||||||
|
'user@someserver',
|
||||||
|
'user@someserver',
|
||||||
|
'user@someserver')
|
||||||
|
|
||||||
|
def testJIDNoUser(self):
|
||||||
|
"""Test JID of the form 'component.domain.tld'."""
|
||||||
|
self.check_JID(JID('component.someserver'),
|
||||||
|
'',
|
||||||
|
'component.someserver',
|
||||||
|
'',
|
||||||
|
'component.someserver',
|
||||||
|
'component.someserver',
|
||||||
|
'component.someserver')
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestJIDClass)
|
||||||
|
|||||||
@@ -56,4 +56,21 @@ class TestErrorStanzas(SleekTest):
|
|||||||
</message>
|
</message>
|
||||||
""", use_values=False)
|
""", use_values=False)
|
||||||
|
|
||||||
|
def testDelText(self):
|
||||||
|
"""Test deleting the text of an error."""
|
||||||
|
msg = self.Message()
|
||||||
|
msg['error']['test'] = 'Error!'
|
||||||
|
msg['error']['condition'] = 'internal-server-error'
|
||||||
|
|
||||||
|
del msg['error']['text']
|
||||||
|
|
||||||
|
self.check_message(msg, """
|
||||||
|
<message type="error">
|
||||||
|
<error type="cancel">
|
||||||
|
<internal-server-error xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
|
</error>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas)
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestErrorStanzas)
|
||||||
|
|||||||
110
tests/test_stream_exceptions.py
Normal file
110
tests/test_stream_exceptions.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
import sys
|
||||||
|
import sleekxmpp
|
||||||
|
from sleekxmpp.exceptions import XMPPError
|
||||||
|
from sleekxmpp.test import *
|
||||||
|
|
||||||
|
|
||||||
|
class TestStreamExceptions(SleekTest):
|
||||||
|
"""
|
||||||
|
Test handling roster updates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.stream_close()
|
||||||
|
|
||||||
|
def testXMPPErrorException(self):
|
||||||
|
"""Test raising an XMPPError exception."""
|
||||||
|
|
||||||
|
def message(msg):
|
||||||
|
raise XMPPError(condition='feature-not-implemented',
|
||||||
|
text="We don't do things that way here.",
|
||||||
|
etype='cancel',
|
||||||
|
extension='foo',
|
||||||
|
extension_ns='foo:error',
|
||||||
|
extension_args={'test': 'true'})
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('message', message)
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<message>
|
||||||
|
<body>This is going to cause an error.</body>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.stream_send_message("""
|
||||||
|
<message type="error">
|
||||||
|
<error type="cancel">
|
||||||
|
<feature-not-implemented
|
||||||
|
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
|
We don't do things that way here.
|
||||||
|
</text>
|
||||||
|
<foo xmlns="foo:error" test="true" />
|
||||||
|
</error>
|
||||||
|
</message>
|
||||||
|
""", use_values=False)
|
||||||
|
|
||||||
|
def testThreadedXMPPErrorException(self):
|
||||||
|
"""Test raising an XMPPError exception in a threaded handler."""
|
||||||
|
|
||||||
|
def message(msg):
|
||||||
|
raise XMPPError(condition='feature-not-implemented',
|
||||||
|
text="We don't do things that way here.",
|
||||||
|
etype='cancel')
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('message', message,
|
||||||
|
threaded=True)
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<message>
|
||||||
|
<body>This is going to cause an error.</body>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.stream_send_message("""
|
||||||
|
<message type="error">
|
||||||
|
<error type="cancel">
|
||||||
|
<feature-not-implemented
|
||||||
|
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
|
We don't do things that way here.
|
||||||
|
</text>
|
||||||
|
</error>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
def testUnknownException(self):
|
||||||
|
"""Test raising an generic exception in a threaded handler."""
|
||||||
|
|
||||||
|
def message(msg):
|
||||||
|
raise ValueError("Did something wrong")
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('message', message)
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<message>
|
||||||
|
<body>This is going to cause an error.</body>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
self.stream_send_message("""
|
||||||
|
<message type="error">
|
||||||
|
<error type="cancel">
|
||||||
|
<undefined-condition
|
||||||
|
xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" />
|
||||||
|
<text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas">
|
||||||
|
SleekXMPP got into trouble.
|
||||||
|
</text>
|
||||||
|
</error>
|
||||||
|
</message>
|
||||||
|
""")
|
||||||
|
else:
|
||||||
|
# Unfortunately, tracebacks do not make for very portable tests.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamExceptions)
|
||||||
188
tests/test_stream_presence.py
Normal file
188
tests/test_stream_presence.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import time
|
||||||
|
from sleekxmpp.test import *
|
||||||
|
|
||||||
|
|
||||||
|
class TestStreamPresence(SleekTest):
|
||||||
|
"""
|
||||||
|
Test handling roster updates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.stream_close()
|
||||||
|
|
||||||
|
def testInitialUnavailablePresences(self):
|
||||||
|
"""
|
||||||
|
Test receiving unavailable presences from JIDs that
|
||||||
|
are not online.
|
||||||
|
"""
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def got_offline(presence):
|
||||||
|
# The got_offline event should not be triggered.
|
||||||
|
events.add('got_offline')
|
||||||
|
|
||||||
|
def unavailable(presence):
|
||||||
|
# The presence_unavailable event should be triggered.
|
||||||
|
events.add('unavailable')
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('got_offline', got_offline)
|
||||||
|
self.xmpp.add_event_handler('presence_unavailable', unavailable)
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence type="unavailable" from="otheruser@localhost" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Give event queue time to process.
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.assertEqual(events, set(('unavailable',)),
|
||||||
|
"Got offline incorrectly triggered: %s." % events)
|
||||||
|
|
||||||
|
def testGotOffline(self):
|
||||||
|
"""Test that got_offline is triggered properly."""
|
||||||
|
events = []
|
||||||
|
|
||||||
|
def got_offline(presence):
|
||||||
|
events.append('got_offline')
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('got_offline', got_offline)
|
||||||
|
|
||||||
|
# Setup roster. Use a 'set' instead of 'result' so we
|
||||||
|
# don't have to handle get_roster() blocking.
|
||||||
|
#
|
||||||
|
# We use the stream to initialize the roster to make
|
||||||
|
# the test independent of the roster implementation.
|
||||||
|
self.stream_recv("""
|
||||||
|
<iq type="set">
|
||||||
|
<query xmlns="jabber:iq:roster">
|
||||||
|
<item jid="otheruser@localhost"
|
||||||
|
name="Other User"
|
||||||
|
subscription="both">
|
||||||
|
<group>Testers</group>
|
||||||
|
</item>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Contact comes online.
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence from="otheruser@localhost/foobar" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Contact goes offline, should trigger got_offline.
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence from="otheruser@localhost/foobar"
|
||||||
|
type="unavailable" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Give event queue time to process.
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
self.assertEqual(events, ['got_offline'],
|
||||||
|
"Got offline incorrectly triggered: %s" % events)
|
||||||
|
|
||||||
|
def testGotOnline(self):
|
||||||
|
"""Test that got_online is triggered properly."""
|
||||||
|
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def presence_available(p):
|
||||||
|
events.add('presence_available')
|
||||||
|
|
||||||
|
def got_online(p):
|
||||||
|
events.add('got_online')
|
||||||
|
|
||||||
|
self.stream_start()
|
||||||
|
self.xmpp.add_event_handler('presence_available', presence_available)
|
||||||
|
self.xmpp.add_event_handler('got_online', got_online)
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence from="user@localhost" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Give event queue time to process.
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
expected = set(('presence_available', 'got_online'))
|
||||||
|
self.assertEqual(events, expected,
|
||||||
|
"Incorrect events triggered: %s" % events)
|
||||||
|
|
||||||
|
def testAutoAuthorizeAndSubscribe(self):
|
||||||
|
"""
|
||||||
|
Test auto authorizing and auto subscribing
|
||||||
|
to subscription requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def presence_subscribe(p):
|
||||||
|
events.add('presence_subscribe')
|
||||||
|
|
||||||
|
def changed_subscription(p):
|
||||||
|
events.add('changed_subscription')
|
||||||
|
|
||||||
|
self.stream_start(jid='tester@localhost')
|
||||||
|
|
||||||
|
self.xmpp.add_event_handler('changed_subscription',
|
||||||
|
changed_subscription)
|
||||||
|
self.xmpp.add_event_handler('presence_subscribe',
|
||||||
|
presence_subscribe)
|
||||||
|
|
||||||
|
# With these settings we should accept a subscription
|
||||||
|
# and request a subscription in return.
|
||||||
|
self.xmpp.auto_authorize = True
|
||||||
|
self.xmpp.auto_subscribe = True
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence from="user@localhost" type="subscribe" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.stream_send_presence("""
|
||||||
|
<presence to="user@localhost" type="subscribed" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.stream_send_presence("""
|
||||||
|
<presence to="user@localhost" type="subscribe" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
expected = set(('presence_subscribe', 'changed_subscription'))
|
||||||
|
self.assertEqual(events, expected,
|
||||||
|
"Incorrect events triggered: %s" % events)
|
||||||
|
|
||||||
|
def testNoAutoAuthorize(self):
|
||||||
|
"""Test auto rejecting subscription requests."""
|
||||||
|
|
||||||
|
events = set()
|
||||||
|
|
||||||
|
def presence_subscribe(p):
|
||||||
|
events.add('presence_subscribe')
|
||||||
|
|
||||||
|
def changed_subscription(p):
|
||||||
|
events.add('changed_subscription')
|
||||||
|
|
||||||
|
self.stream_start(jid='tester@localhost')
|
||||||
|
|
||||||
|
self.xmpp.add_event_handler('changed_subscription',
|
||||||
|
changed_subscription)
|
||||||
|
self.xmpp.add_event_handler('presence_subscribe',
|
||||||
|
presence_subscribe)
|
||||||
|
|
||||||
|
# With this setting we should reject all subscriptions.
|
||||||
|
self.xmpp.auto_authorize = False
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<presence from="user@localhost" type="subscribe" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.stream_send_presence("""
|
||||||
|
<presence to="user@localhost" type="unsubscribed" />
|
||||||
|
""")
|
||||||
|
|
||||||
|
expected = set(('presence_subscribe', 'changed_subscription'))
|
||||||
|
self.assertEqual(events, expected,
|
||||||
|
"Incorrect events triggered: %s" % events)
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamPresence)
|
||||||
86
tests/test_stream_roster.py
Normal file
86
tests/test_stream_roster.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from sleekxmpp.test import *
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class TestStreamRoster(SleekTest):
|
||||||
|
"""
|
||||||
|
Test handling roster updates.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
self.stream_close()
|
||||||
|
|
||||||
|
def testGetRoster(self):
|
||||||
|
"""Test handling roster requests."""
|
||||||
|
self.stream_start(mode='client')
|
||||||
|
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
|
||||||
|
|
||||||
|
# Since get_roster blocks, we need to run it in a thread.
|
||||||
|
t = threading.Thread(name='get_roster', target=self.xmpp.get_roster)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
self.stream_send_iq("""
|
||||||
|
<iq type="get" id="1">
|
||||||
|
<query xmlns="jabber:iq:roster" />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
self.stream_recv("""
|
||||||
|
<iq type="result" id="1">
|
||||||
|
<query xmlns="jabber:iq:roster">
|
||||||
|
<item jid="user@localhost"
|
||||||
|
name="User"
|
||||||
|
subscription="both">
|
||||||
|
<group>Friends</group>
|
||||||
|
<group>Examples</group>
|
||||||
|
</item>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Wait for get_roster to return.
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
roster = {'user@localhost': {'name': 'User',
|
||||||
|
'subscription': 'both',
|
||||||
|
'groups': ['Friends', 'Examples'],
|
||||||
|
'presence': {},
|
||||||
|
'in_roster': True}}
|
||||||
|
self.failUnless(self.xmpp.roster == roster,
|
||||||
|
"Unexpected roster values: %s" % self.xmpp.roster)
|
||||||
|
|
||||||
|
def testRosterSet(self):
|
||||||
|
"""Test handling pushed roster updates."""
|
||||||
|
self.stream_start(mode='client')
|
||||||
|
self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.")
|
||||||
|
|
||||||
|
self.stream_recv("""
|
||||||
|
<iq type="set" id="1">
|
||||||
|
<query xmlns="jabber:iq:roster">
|
||||||
|
<item jid="user@localhost"
|
||||||
|
name="User"
|
||||||
|
subscription="both">
|
||||||
|
<group>Friends</group>
|
||||||
|
<group>Examples</group>
|
||||||
|
</item>
|
||||||
|
</query>
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
self.stream_send_iq("""
|
||||||
|
<iq type="result" id="1">
|
||||||
|
<query xmlns="jabber:iq:roster" />
|
||||||
|
</iq>
|
||||||
|
""")
|
||||||
|
|
||||||
|
roster = {'user@localhost': {'name': 'User',
|
||||||
|
'subscription': 'both',
|
||||||
|
'groups': ['Friends', 'Examples'],
|
||||||
|
'presence': {},
|
||||||
|
'in_roster': True}}
|
||||||
|
self.failUnless(self.xmpp.roster == roster,
|
||||||
|
"Unexpected roster values: %s" % self.xmpp.roster)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster)
|
||||||
@@ -73,6 +73,16 @@ class TestToString(SleekTest):
|
|||||||
message="The xmlns parameter was not used properly.",
|
message="The xmlns parameter was not used properly.",
|
||||||
xmlns='foo')
|
xmlns='foo')
|
||||||
|
|
||||||
|
def testTailContent(self):
|
||||||
|
"""
|
||||||
|
Test that elements of the form <a>foo <b>bar</b> baz</a> only
|
||||||
|
include " baz" once.
|
||||||
|
"""
|
||||||
|
self.tryTostring(
|
||||||
|
original='<a>foo <b>bar</b> baz</a>',
|
||||||
|
message='Element tail content is incorrect.')
|
||||||
|
|
||||||
|
|
||||||
def testStanzaNs(self):
|
def testStanzaNs(self):
|
||||||
"""
|
"""
|
||||||
Test using the stanza_ns tostring parameter, which will prevent
|
Test using the stanza_ns tostring parameter, which will prevent
|
||||||
|
|||||||
Reference in New Issue
Block a user