# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.words.protocols.jabber.xmlstream}.
"""
from __future__ import absolute_import, division
from twisted.trial import unittest
from zope.interface.verify import verifyObject
from twisted.internet import defer, task
from twisted.internet.error import ConnectionLost
from twisted.internet.interfaces import IProtocolFactory
from twisted.python import failure
from twisted.python.compat import unicode
from twisted.test import proto_helpers
from twisted.words.test.test_xmlstream import GenericXmlStreamFactoryTestsMixin
from twisted.words.xish import domish
from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream
try:
from twisted.internet import ssl
except ImportError:
ssl = None
skipWhenNoSSL = "SSL not available"
else:
skipWhenNoSSL = None
from twisted.internet.ssl import CertificateOptions
from twisted.internet._sslverify import ClientTLSOptions
NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
class HashPasswordTests(unittest.TestCase):
"""
Tests for L{xmlstream.hashPassword}.
"""
def test_basic(self):
"""
The sid and secret are concatenated to calculate sha1 hex digest.
"""
hash = xmlstream.hashPassword(u"12345", u"secret")
self.assertEqual('99567ee91b2c7cabf607f10cb9f4a3634fa820e0', hash)
def test_sidNotUnicode(self):
"""
The session identifier must be a unicode object.
"""
self.assertRaises(TypeError, xmlstream.hashPassword, b"\xc2\xb92345",
u"secret")
def test_passwordNotUnicode(self):
"""
The password must be a unicode object.
"""
self.assertRaises(TypeError, xmlstream.hashPassword, u"12345",
b"secr\xc3\xa9t")
def test_unicodeSecret(self):
"""
The concatenated sid and password must be encoded to UTF-8 before hashing.
"""
hash = xmlstream.hashPassword(u"12345", u"secr\u00e9t")
self.assertEqual('659bf88d8f8e179081f7f3b4a8e7d224652d2853', hash)
class IQTests(unittest.TestCase):
"""
Tests both IQ and the associated IIQResponseTracker callback.
"""
def setUp(self):
authenticator = xmlstream.ConnectAuthenticator('otherhost')
authenticator.namespace = 'testns'
self.xmlstream = xmlstream.XmlStream(authenticator)
self.clock = task.Clock()
self.xmlstream._callLater = self.clock.callLater
self.xmlstream.makeConnection(proto_helpers.StringTransport())
self.xmlstream.dataReceived(
"<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
"xmlns='testns' from='otherhost' version='1.0'>")
self.iq = xmlstream.IQ(self.xmlstream, 'get')
def testBasic(self):
self.assertEqual(self.iq['type'], 'get')
self.assertTrue(self.iq['id'])
def testSend(self):
self.xmlstream.transport.clear()
self.iq.send()
idBytes = self.iq['id'].encode('utf-8')
self.assertIn(self.xmlstream.transport.value(), [
b"<iq type='get' id='" + idBytes + b"'/>",
b"<iq id='" + idBytes + b"' type='get'/>"
])
def testResultResponse(self):
def cb(result):
self.assertEqual(result['type'], 'result')
d = self.iq.send()
d.addCallback(cb)
xs = self.xmlstream
xs.dataReceived("<iq type='result' id='%s'/>" % self.iq['id'])
return d
def testErrorResponse(self):
d = self.iq.send()
self.assertFailure(d, error.StanzaError)
xs = self.xmlstream
xs.dataReceived("<iq type='error' id='%s'/>" % self.iq['id'])
return d
def testNonTrackedResponse(self):
"""
Test that untracked iq responses don't trigger any action.
Untracked means that the id of the incoming response iq is not
in the stream's C{iqDeferreds} dictionary.
"""
xs = self.xmlstream
xmlstream.upgradeWithIQResponseTracker(xs)
# Make sure we aren't tracking any iq's.
self.assertFalse(xs.iqDeferreds)
# Set up a fallback handler that checks the stanza's handled attribute.
# If that is set to True, the iq tracker claims to have handled the
# response.
def cb(iq):
self.assertFalse(getattr(iq, 'handled', False))
xs.addObserver("/iq", cb, -1)
# Receive an untracked iq response
xs.dataReceived("<iq type='result' id='test'/>")
def testCleanup(self):
"""
Test if the deferred associated with an iq request is removed
from the list kept in the L{XmlStream} object after it has
been fired.
"""
d = self.iq.send()
xs = self.xmlstream
xs.dataReceived("<iq type='result' id='%s'/>" % self.iq['id'])
self.assertNotIn(self.iq['id'], xs.iqDeferreds)
return d
def testDisconnectCleanup(self):
"""
Test if deferreds for iq's that haven't yet received a response
have their errback called on stream disconnect.
"""
d = self.iq.send()
xs = self.xmlstream
xs.connectionLost("Closed by peer")
self.assertFailure(d, ConnectionLost)
return d
def testNoModifyingDict(self):
"""
Test to make sure the errbacks cannot cause the iteration of the
iqDeferreds to blow up in our face.
"""
def eb(failure):
d = xmlstream.IQ(self.xmlstream).send()
d.addErrback(eb)
d = self.iq.send()
d.addErrback(eb)
self.xmlstream.connectionLost("Closed by peer")
return d
def testRequestTimingOut(self):
"""
Test that an iq request with a defined timeout times out.
"""
self.iq.timeout = 60
d = self.iq.send()
self.assertFailure(d, xmlstream.TimeoutError)
self.clock.pump([1, 60])
self.assertFalse(self.clock.calls)
self.assertFalse(self.xmlstream.iqDeferreds)
return d
def testRequestNotTimingOut(self):
"""
Test that an iq request with a defined timeout does not time out
when a response was received before the timeout period elapsed.
"""
self.iq.timeout = 60
d = self.iq.send()
self.clock.callLater(1, self.xmlstream.dataReceived,
"<iq type='result' id='%s'/>" % self.iq['id'])
self.clock.pump([1, 1])
self.assertFalse(self.clock.calls)
return d
def testDisconnectTimeoutCancellation(self):
"""
Test if timeouts for iq's that haven't yet received a response
are cancelled on stream disconnect.
"""
self.iq.timeout = 60
d = self.iq.send()
xs = self.xmlstream
xs.connectionLost("Closed by peer")
self.assertFailure(d, ConnectionLost)
self.assertFalse(self.clock.calls)
return d
class XmlStreamTests(unittest.TestCase):
def onStreamStart(self, obj):
self.gotStreamStart = True
def onStreamEnd(self, obj):
self.gotStreamEnd = True
def onStreamError(self, obj):
self.gotStreamError = True
def setUp(self):
"""
Set up XmlStream and several observers.
"""
self.gotStreamStart = False
self.gotStreamEnd = False
self.gotStreamError = False
xs = xmlstream.XmlStream(xmlstream.Authenticator())
xs.addObserver('//event/stream/start', self.onStreamStart)
xs.addObserver('//event/stream/end', self.onStreamEnd)
xs.addObserver('//event/stream/error', self.onStreamError)
xs.makeConnection(proto_helpers.StringTransportWithDisconnection())
xs.transport.protocol = xs
xs.namespace = 'testns'
xs.version = (1, 0)
self.xmlstream = xs
def test_sendHeaderBasic(self):
"""
Basic test on the header sent by sendHeader.
"""
xs = self.xmlstream
xs.sendHeader()
splitHeader = self.xmlstream.transport.value()[0:-1].split(b' ')
self.assertIn(b"<stream:stream", splitHeader)
self.assertIn(b"xmlns:stream='http://etherx.jabber.org/streams'",
splitHeader)
self.assertIn(b"xmlns='testns'", splitHeader)
self.assertIn(b"version='1.0'", splitHeader)
self.assertTrue(xs._headerSent)
def test_sendHeaderAdditionalNamespaces(self):
"""
Test for additional namespace declarations.
"""
xs = self.xmlstream
xs.prefixes['jabber:server:dialback'] = 'db'
xs.sendHeader()
splitHeader = self.xmlstream.transport.value()[0:-1].split(b' ')
self.assertIn(b"<stream:stream", splitHeader)
self.assertIn(b"xmlns:stream='http://etherx.jabber.org/streams'",
splitHeader)
self.assertIn(b"xmlns:db='jabber:server:dialback'", splitHeader)
self.assertIn(b"xmlns='testns'", splitHeader)
self.assertIn(b"version='1.0'", splitHeader)
self.assertTrue(xs._headerSent)
def test_sendHeaderInitiating(self):
"""
Test addressing when initiating a stream.
"""
xs = self.xmlstream
xs.thisEntity = jid.JID('thisHost')
xs.otherEntity = jid.JID('otherHost')
xs.initiating = True
xs.sendHeader()
splitHeader = xs.transport.value()[0:-1].split(b' ')
self.assertIn(b"to='otherhost'", splitHeader)
self.assertIn(b"from='thishost'", splitHeader)
def test_sendHeaderReceiving(self):
"""
Test addressing when receiving a stream.
"""
xs = self.xmlstream
xs.thisEntity = jid.JID('thisHost')
xs.otherEntity = jid.JID('otherHost')
xs.initiating = False
xs.sid = 'session01'
xs.sendHeader()
splitHeader = xs.transport.value()[0:-1].split(b' ')
self.assertIn(b"to='otherhost'", splitHeader)
self.assertIn(b"from='thishost'", splitHeader)
self.assertIn(b"id='session01'", splitHeader)
def test_receiveStreamError(self):
"""
Test events when a stream error is received.
"""
xs = self.xmlstream
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345' version='1.0'>")
xs.dataReceived("<stream:error/>")
self.assertTrue(self.gotStreamError)
self.assertTrue(self.gotStreamEnd)
def test_sendStreamErrorInitiating(self):
"""
Test sendStreamError on an initiating xmlstream with a header sent.
An error should be sent out and the connection lost.
"""
xs = self.xmlstream
xs.initiating = True
xs.sendHeader()
xs.transport.clear()
xs.sendStreamError(error.StreamError('version-unsupported'))
self.assertNotEqual(b'', xs.transport.value())
self.assertTrue(self.gotStreamEnd)
def test_sendStreamErrorInitiatingNoHeader(self):
"""
Test sendStreamError on an initiating xmlstream without having sent a
header.
In this case, no header should be generated. Also, the error should
not be sent out on the stream. Just closing the connection.
"""
xs = self.xmlstream
xs.initiating = True
xs.transport.clear()
xs.sendStreamError(error.StreamError('version-unsupported'))
self.assertNot(xs._headerSent)
self.assertEqual(b'', xs.transport.value())
self.assertTrue(self.gotStreamEnd)
def test_sendStreamErrorReceiving(self):
"""
Test sendStreamError on a receiving xmlstream with a header sent.
An error should be sent out and the connection lost.
"""
xs = self.xmlstream
xs.initiating = False
xs.sendHeader()
xs.transport.clear()
xs.sendStreamError(error.StreamError('version-unsupported'))
self.assertNotEqual(b'', xs.transport.value())
self.assertTrue(self.gotStreamEnd)
def test_sendStreamErrorReceivingNoHeader(self):
"""
Test sendStreamError on a receiving xmlstream without having sent a
header.
In this case, a header should be generated. Then, the error should
be sent out on the stream followed by closing the connection.
"""
xs = self.xmlstream
xs.initiating = False
xs.transport.clear()
xs.sendStreamError(error.StreamError('version-unsupported'))
self.assertTrue(xs._headerSent)
self.assertNotEqual(b'', xs.transport.value())
self.assertTrue(self.gotStreamEnd)
def test_reset(self):
"""
Test resetting the XML stream to start a new layer.
"""
xs = self.xmlstream
xs.sendHeader()
stream = xs.stream
xs.reset()
self.assertNotEqual(stream, xs.stream)
self.assertNot(xs._headerSent)
def test_send(self):
"""
Test send with various types of objects.
"""
xs = self.xmlstream
xs.send('<presence/>')
self.assertEqual(xs.transport.value(), b'<presence/>')
xs.transport.clear()
el = domish.Element(('testns', 'presence'))
xs.send(el)
self.assertEqual(xs.transport.value(), b'<presence/>')
xs.transport.clear()
el = domish.Element(('http://etherx.jabber.org/streams', 'features'))
xs.send(el)
self.assertEqual(xs.transport.value(), b'<stream:features/>')
def test_authenticator(self):
"""
Test that the associated authenticator is correctly called.
"""
connectionMadeCalls = []
streamStartedCalls = []
associateWithStreamCalls = []
class TestAuthenticator:
def connectionMade(self):
connectionMadeCalls.append(None)
def streamStarted(self, rootElement):
streamStartedCalls.append(rootElement)
def associateWithStream(self, xs):
associateWithStreamCalls.append(xs)
a = TestAuthenticator()
xs = xmlstream.XmlStream(a)
self.assertEqual([xs], associateWithStreamCalls)
xs.connectionMade()
self.assertEqual([None], connectionMadeCalls)
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345'>")
self.assertEqual(1, len(streamStartedCalls))
xs.reset()
self.assertEqual([None], connectionMadeCalls)
class TestError(Exception):
pass
class AuthenticatorTests(unittest.TestCase):
def setUp(self):
self.authenticator = xmlstream.Authenticator()
self.xmlstream = xmlstream.XmlStream(self.authenticator)
def test_streamStart(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.org' to='example.com' id='12345' "
"version='1.0'>")
self.assertEqual((1, 0), xs.version)
self.assertIdentical(None, xs.sid)
self.assertEqual('invalid', xs.namespace)
self.assertIdentical(None, xs.otherEntity)
self.assertEqual(None, xs.thisEntity)
def test_streamStartLegacy(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header for a pre-XMPP-1.0 header.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345'>")
self.assertEqual((0, 0), xs.version)
def test_streamBadVersionOneDigit(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header for a version with only one digit.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345' version='1'>")
self.assertEqual((0, 0), xs.version)
def test_streamBadVersionNoNumber(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header for a malformed version.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345' version='blah'>")
self.assertEqual((0, 0), xs.version)
class ConnectAuthenticatorTests(unittest.TestCase):
def setUp(self):
self.gotAuthenticated = False
self.initFailure = None
self.authenticator = xmlstream.ConnectAuthenticator('otherHost')
self.xmlstream = xmlstream.XmlStream(self.authenticator)
self.xmlstream.addObserver('//event/stream/authd', self.onAuthenticated)
self.xmlstream.addObserver('//event/xmpp/initfailed', self.onInitFailed)
def onAuthenticated(self, obj):
self.gotAuthenticated = True
def onInitFailed(self, failure):
self.initFailure = failure
def testSucces(self):
"""
Test successful completion of an initialization step.
"""
class Initializer:
def initialize(self):
pass
init = Initializer()
self.xmlstream.initializers = [init]
self.authenticator.initializeStream()
self.assertEqual([], self.xmlstream.initializers)
self.assertTrue(self.gotAuthenticated)
def testFailure(self):
"""
Test failure of an initialization step.
"""
class Initializer:
def initialize(self):
raise TestError
init = Initializer()
self.xmlstream.initializers = [init]
self.authenticator.initializeStream()
self.assertEqual([init], self.xmlstream.initializers)
self.assertFalse(self.gotAuthenticated)
self.assertNotIdentical(None, self.initFailure)
self.assertTrue(self.initFailure.check(TestError))
def test_streamStart(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header.
"""
self.authenticator.namespace = 'testns'
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' to='example.org' id='12345' "
"version='1.0'>")
self.assertEqual((1, 0), xs.version)
self.assertEqual('12345', xs.sid)
self.assertEqual('testns', xs.namespace)
self.assertEqual('example.com', xs.otherEntity.host)
self.assertIdentical(None, xs.thisEntity)
self.assertNot(self.gotAuthenticated)
xs.dataReceived("<stream:features>"
"<test xmlns='testns'/>"
"</stream:features>")
self.assertIn(('testns', 'test'), xs.features)
self.assertTrue(self.gotAuthenticated)
class ListenAuthenticatorTests(unittest.TestCase):
"""
Tests for L{xmlstream.ListenAuthenticator}
"""
def setUp(self):
self.authenticator = xmlstream.ListenAuthenticator()
self.xmlstream = xmlstream.XmlStream(self.authenticator)
def test_streamStart(self):
"""
Test streamStart to fill the appropriate attributes from the
stream header.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
self.assertIdentical(None, xs.sid)
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.org' to='example.com' id='12345' "
"version='1.0'>")
self.assertEqual((1, 0), xs.version)
self.assertNotIdentical(None, xs.sid)
self.assertNotEqual('12345', xs.sid)
self.assertEqual('jabber:client', xs.namespace)
self.assertIdentical(None, xs.otherEntity)
self.assertEqual('example.com', xs.thisEntity.host)
def test_streamStartUnicodeSessionID(self):
"""
The generated session id must be a unicode object.
"""
xs = self.xmlstream
xs.makeConnection(proto_helpers.StringTransport())
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.org' to='example.com' id='12345' "
"version='1.0'>")
self.assertIsInstance(xs.sid, unicode)
class TLSInitiatingInitializerTests(unittest.TestCase):
def setUp(self):
self.output = []
self.done = []
self.savedSSL = xmlstream.ssl
self.authenticator = xmlstream.ConnectAuthenticator(u'example.com')
self.xmlstream = xmlstream.XmlStream(self.authenticator)
self.xmlstream.send = self.output.append
self.xmlstream.connectionMade()
self.xmlstream.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345' version='1.0'>")
self.init = xmlstream.TLSInitiatingInitializer(self.xmlstream)
def tearDown(self):
xmlstream.ssl = self.savedSSL
def test_initRequired(self):
"""
Passing required sets the instance variable.
"""
self.init = xmlstream.TLSInitiatingInitializer(self.xmlstream,
required=True)
self.assertTrue(self.init.required)
def test_wantedSupported(self):
"""
When TLS is wanted and SSL available, StartTLS is initiated.
"""
self.xmlstream.transport = proto_helpers.StringTransport()
self.xmlstream.transport.startTLS = lambda ctx: self.done.append('TLS')
self.xmlstream.reset = lambda: self.done.append('reset')
self.xmlstream.sendHeader = lambda: self.done.append('header')
d = self.init.start()
d.addCallback(self.assertEqual, xmlstream.Reset)
self.assertEqual(2, len(self.output))
starttls = self.output[1]
self.assertEqual('starttls', starttls.name)
self.assertEqual(NS_XMPP_TLS, starttls.uri)
self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
self.assertEqual(['TLS', 'reset', 'header'], self.done)
return d
test_wantedSupported.skip = skipWhenNoSSL
def test_certificateVerify(self):
"""
The server certificate will be verified.
"""
def fakeStartTLS(contextFactory):
self.assertIsInstance(contextFactory, ClientTLSOptions)
self.assertEqual(contextFactory._hostname, u"example.com")
self.done.append('TLS')
self.xmlstream.transport = proto_helpers.StringTransport()
self.xmlstream.transport.startTLS = fakeStartTLS
self.xmlstream.reset = lambda: self.done.append('reset')
self.xmlstream.sendHeader = lambda: self.done.append('header')
d = self.init.start()
self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
self.assertEqual(['TLS', 'reset', 'header'], self.done)
return d
test_certificateVerify.skip = skipWhenNoSSL
def test_certificateVerifyContext(self):
"""
A custom contextFactory is passed through to startTLS.
"""
ctx = CertificateOptions()
self.init = xmlstream.TLSInitiatingInitializer(
self.xmlstream, configurationForTLS=ctx)
self.init.contextFactory = ctx
def fakeStartTLS(contextFactory):
self.assertIs(ctx, contextFactory)
self.done.append('TLS')
self.xmlstream.transport = proto_helpers.StringTransport()
self.xmlstream.transport.startTLS = fakeStartTLS
self.xmlstream.reset = lambda: self.done.append('reset')
self.xmlstream.sendHeader = lambda: self.done.append('header')
d = self.init.start()
self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
self.assertEqual(['TLS', 'reset', 'header'], self.done)
return d
test_certificateVerifyContext.skip = skipWhenNoSSL
def test_wantedNotSupportedNotRequired(self):
"""
No StartTLS is initiated when wanted, not required, SSL not available.
"""
xmlstream.ssl = None
self.init.required = False
d = self.init.start()
d.addCallback(self.assertEqual, None)
self.assertEqual(1, len(self.output))
return d
def test_wantedNotSupportedRequired(self):
"""
TLSNotSupported is raised when TLS is required but not available.
"""
xmlstream.ssl = None
self.init.required = True
d = self.init.start()
self.assertFailure(d, xmlstream.TLSNotSupported)
self.assertEqual(1, len(self.output))
return d
def test_notWantedRequired(self):
"""
TLSRequired is raised when TLS is not wanted, but required by server.
"""
tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
tls.addElement('required')
self.xmlstream.features = {(tls.uri, tls.name): tls}
self.init.wanted = False
d = self.init.start()
self.assertEqual(1, len(self.output))
self.assertFailure(d, xmlstream.TLSRequired)
return d
def test_notWantedNotRequired(self):
"""
No StartTLS is initiated when not wanted and not required.
"""
tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
self.xmlstream.features = {(tls.uri, tls.name): tls}
self.init.wanted = False
self.init.required = False
d = self.init.start()
d.addCallback(self.assertEqual, None)
self.assertEqual(1, len(self.output))
return d
def test_failed(self):
"""
TLSFailed is raised when the server responds with a failure.
"""
# Pretend that ssl is supported, it isn't actually used when the
# server starts out with a failure in response to our initial
# C{starttls} stanza.
xmlstream.ssl = 1
d = self.init.start()
self.assertFailure(d, xmlstream.TLSFailed)
self.xmlstream.dataReceived("<failure xmlns='%s'/>" % NS_XMPP_TLS)
return d
class TestFeatureInitializer(xmlstream.BaseFeatureInitiatingInitializer):
feature = ('testns', 'test')
def start(self):
return defer.succeed(None)
class BaseFeatureInitiatingInitializerTests(unittest.TestCase):
def setUp(self):
self.xmlstream = xmlstream.XmlStream(xmlstream.Authenticator())
self.init = TestFeatureInitializer(self.xmlstream)
def testAdvertized(self):
"""
Test that an advertized feature results in successful initialization.
"""
self.xmlstream.features = {self.init.feature:
domish.Element(self.init.feature)}
return self.init.initialize()
def testNotAdvertizedRequired(self):
"""
Test that when the feature is not advertized, but required by the
initializer, an exception is raised.
"""
self.init.required = True
self.assertRaises(xmlstream.FeatureNotAdvertized, self.init.initialize)
def testNotAdvertizedNotRequired(self):
"""
Test that when the feature is not advertized, and not required by the
initializer, the initializer silently succeeds.
"""
self.init.required = False
self.assertIdentical(None, self.init.initialize())
class ToResponseTests(unittest.TestCase):
def test_toResponse(self):
"""
Test that a response stanza is generated with addressing swapped.
"""
stanza = domish.Element(('jabber:client', 'iq'))
stanza['type'] = 'get'
stanza['to'] = 'user1@example.com'
stanza['from'] = 'user2@example.com/resource'
stanza['id'] = 'stanza1'
response = xmlstream.toResponse(stanza, 'result')
self.assertNotIdentical(stanza, response)
self.assertEqual(response['from'], 'user1@example.com')
self.assertEqual(response['to'], 'user2@example.com/resource')
self.assertEqual(response['type'], 'result')
self.assertEqual(response['id'], 'stanza1')
def test_toResponseNoFrom(self):
"""
Test that a response is generated from a stanza without a from address.
"""
stanza = domish.Element(('jabber:client', 'iq'))
stanza['type'] = 'get'
stanza['to'] = 'user1@example.com'
response = xmlstream.toResponse(stanza)
self.assertEqual(response['from'], 'user1@example.com')
self.assertFalse(response.hasAttribute('to'))
def test_toResponseNoTo(self):
"""
Test that a response is generated from a stanza without a to address.
"""
stanza = domish.Element(('jabber:client', 'iq'))
stanza['type'] = 'get'
stanza['from'] = 'user2@example.com/resource'
response = xmlstream.toResponse(stanza)
self.assertFalse(response.hasAttribute('from'))
self.assertEqual(response['to'], 'user2@example.com/resource')
def test_toResponseNoAddressing(self):
"""
Test that a response is generated from a stanza without any addressing.
"""
stanza = domish.Element(('jabber:client', 'message'))
stanza['type'] = 'chat'
response = xmlstream.toResponse(stanza)
self.assertFalse(response.hasAttribute('to'))
self.assertFalse(response.hasAttribute('from'))
def test_noID(self):
"""
Test that a proper response is generated without id attribute.
"""
stanza = domish.Element(('jabber:client', 'message'))
response = xmlstream.toResponse(stanza)
self.assertFalse(response.hasAttribute('id'))
def test_noType(self):
"""
Test that a proper response is generated without type attribute.
"""
stanza = domish.Element(('jabber:client', 'message'))
response = xmlstream.toResponse(stanza)
self.assertFalse(response.hasAttribute('type'))
class DummyFactory(object):
"""
Dummy XmlStream factory that only registers bootstrap observers.
"""
def __init__(self):
self.callbacks = {}
def addBootstrap(self, event, callback):
self.callbacks[event] = callback
class DummyXMPPHandler(xmlstream.XMPPHandler):
"""
Dummy XMPP subprotocol handler to count the methods are called on it.
"""
def __init__(self):
self.doneMade = 0
self.doneInitialized = 0
self.doneLost = 0
def makeConnection(self, xs):
self.connectionMade()
def connectionMade(self):
self.doneMade += 1
def connectionInitialized(self):
self.doneInitialized += 1
def connectionLost(self, reason):
self.doneLost += 1
class FailureReasonXMPPHandler(xmlstream.XMPPHandler):
"""
Dummy handler specifically for failure Reason tests.
"""
def __init__(self):
self.gotFailureReason = False
def connectionLost(self, reason):
if isinstance(reason, failure.Failure):
self.gotFailureReason = True
class XMPPHandlerTests(unittest.TestCase):
"""
Tests for L{xmlstream.XMPPHandler}.
"""
def test_interface(self):
"""
L{xmlstream.XMPPHandler} implements L{ijabber.IXMPPHandler}.
"""
verifyObject(ijabber.IXMPPHandler, xmlstream.XMPPHandler())
def test_send(self):
"""
Test that data is passed on for sending by the stream manager.
"""
class DummyStreamManager(object):
def __init__(self):
self.outlist = []
def send(self, data):
self.outlist.append(data)
handler = xmlstream.XMPPHandler()
handler.parent = DummyStreamManager()
handler.send('<presence/>')
self.assertEqual(['<presence/>'], handler.parent.outlist)
def test_makeConnection(self):
"""
Test that makeConnection saves the XML stream and calls connectionMade.
"""
class TestXMPPHandler(xmlstream.XMPPHandler):
def connectionMade(self):
self.doneMade = True
handler = TestXMPPHandler()
xs = xmlstream.XmlStream(xmlstream.Authenticator())
handler.makeConnection(xs)
self.assertTrue(handler.doneMade)
self.assertIdentical(xs, handler.xmlstream)
def test_connectionLost(self):
"""
Test that connectionLost forgets the XML stream.
"""
handler = xmlstream.XMPPHandler()
xs = xmlstream.XmlStream(xmlstream.Authenticator())
handler.makeConnection(xs)
handler.connectionLost(Exception())
self.assertIdentical(None, handler.xmlstream)
class XMPPHandlerCollectionTests(unittest.TestCase):
"""
Tests for L{xmlstream.XMPPHandlerCollection}.
"""
def setUp(self):
self.collection = xmlstream.XMPPHandlerCollection()
def test_interface(self):
"""
L{xmlstream.StreamManager} implements L{ijabber.IXMPPHandlerCollection}.
"""
verifyObject(ijabber.IXMPPHandlerCollection, self.collection)
def test_addHandler(self):
"""
Test the addition of a protocol handler.
"""
handler = DummyXMPPHandler()
handler.setHandlerParent(self.collection)
self.assertIn(handler, self.collection)
self.assertIdentical(self.collection, handler.parent)
def test_removeHandler(self):
"""
Test removal of a protocol handler.
"""
handler = DummyXMPPHandler()
handler.setHandlerParent(self.collection)
handler.disownHandlerParent(self.collection)
self.assertNotIn(handler, self.collection)
self.assertIdentical(None, handler.parent)
class StreamManagerTests(unittest.TestCase):
"""
Tests for L{xmlstream.StreamManager}.
"""
def setUp(self):
factory = DummyFactory()
self.streamManager = xmlstream.StreamManager(factory)
def test_basic(self):
"""
Test correct initialization and setup of factory observers.
"""
sm = self.streamManager
self.assertIdentical(None, sm.xmlstream)
self.assertEqual([], sm.handlers)
self.assertEqual(sm._connected,
sm.factory.callbacks['//event/stream/connected'])
self.assertEqual(sm._authd,
sm.factory.callbacks['//event/stream/authd'])
self.assertEqual(sm._disconnected,
sm.factory.callbacks['//event/stream/end'])
self.assertEqual(sm.initializationFailed,
sm.factory.callbacks['//event/xmpp/initfailed'])
def test_connected(self):
"""
Test that protocol handlers have their connectionMade method called
when the XML stream is connected.
"""
sm = self.streamManager
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._connected(xs)
self.assertEqual(1, handler.doneMade)
self.assertEqual(0, handler.doneInitialized)
self.assertEqual(0, handler.doneLost)
def test_connectedLogTrafficFalse(self):
"""
Test raw data functions unset when logTraffic is set to False.
"""
sm = self.streamManager
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._connected(xs)
self.assertIdentical(None, xs.rawDataInFn)
self.assertIdentical(None, xs.rawDataOutFn)
def test_connectedLogTrafficTrue(self):
"""
Test raw data functions set when logTraffic is set to True.
"""
sm = self.streamManager
sm.logTraffic = True
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._connected(xs)
self.assertNotIdentical(None, xs.rawDataInFn)
self.assertNotIdentical(None, xs.rawDataOutFn)
def test_authd(self):
"""
Test that protocol handlers have their connectionInitialized method
called when the XML stream is initialized.
"""
sm = self.streamManager
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._authd(xs)
self.assertEqual(0, handler.doneMade)
self.assertEqual(1, handler.doneInitialized)
self.assertEqual(0, handler.doneLost)
def test_disconnected(self):
"""
Test that protocol handlers have their connectionLost method
called when the XML stream is disconnected.
"""
sm = self.streamManager
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._disconnected(xs)
self.assertEqual(0, handler.doneMade)
self.assertEqual(0, handler.doneInitialized)
self.assertEqual(1, handler.doneLost)
def test_disconnectedReason(self):
"""
A L{STREAM_END_EVENT} results in L{StreamManager} firing the handlers
L{connectionLost} methods, passing a L{failure.Failure} reason.
"""
sm = self.streamManager
handler = FailureReasonXMPPHandler()
handler.setHandlerParent(sm)
sm._disconnected(failure.Failure(Exception("no reason")))
self.assertEqual(True, handler.gotFailureReason)
def test_addHandler(self):
"""
Test the addition of a protocol handler while not connected.
"""
sm = self.streamManager
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
self.assertEqual(0, handler.doneMade)
self.assertEqual(0, handler.doneInitialized)
self.assertEqual(0, handler.doneLost)
def test_addHandlerInitialized(self):
"""
Test the addition of a protocol handler after the stream
have been initialized.
Make sure that the handler will have the connected stream
passed via C{makeConnection} and have C{connectionInitialized}
called.
"""
sm = self.streamManager
xs = xmlstream.XmlStream(xmlstream.Authenticator())
sm._connected(xs)
sm._authd(xs)
handler = DummyXMPPHandler()
handler.setHandlerParent(sm)
self.assertEqual(1, handler.doneMade)
self.assertEqual(1, handler.doneInitialized)
self.assertEqual(0, handler.doneLost)
def test_sendInitialized(self):
"""
Test send when the stream has been initialized.
The data should be sent directly over the XML stream.
"""
factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
sm = xmlstream.StreamManager(factory)
xs = factory.buildProtocol(None)
xs.transport = proto_helpers.StringTransport()
xs.connectionMade()
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345'>")
xs.dispatch(xs, "//event/stream/authd")
sm.send("<presence/>")
self.assertEqual(b"<presence/>", xs.transport.value())
def test_sendNotConnected(self):
"""
Test send when there is no established XML stream.
The data should be cached until an XML stream has been established and
initialized.
"""
factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
sm = xmlstream.StreamManager(factory)
handler = DummyXMPPHandler()
sm.addHandler(handler)
xs = factory.buildProtocol(None)
xs.transport = proto_helpers.StringTransport()
sm.send("<presence/>")
self.assertEqual(b"", xs.transport.value())
self.assertEqual("<presence/>", sm._packetQueue[0])
xs.connectionMade()
self.assertEqual(b"", xs.transport.value())
self.assertEqual("<presence/>", sm._packetQueue[0])
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345'>")
xs.dispatch(xs, "//event/stream/authd")
self.assertEqual(b"<presence/>", xs.transport.value())
self.assertFalse(sm._packetQueue)
def test_sendNotInitialized(self):
"""
Test send when the stream is connected but not yet initialized.
The data should be cached until the XML stream has been initialized.
"""
factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
sm = xmlstream.StreamManager(factory)
xs = factory.buildProtocol(None)
xs.transport = proto_helpers.StringTransport()
xs.connectionMade()
xs.dataReceived("<stream:stream xmlns='jabber:client' "
"xmlns:stream='http://etherx.jabber.org/streams' "
"from='example.com' id='12345'>")
sm.send("<presence/>")
self.assertEqual(b"", xs.transport.value())
self.assertEqual("<presence/>", sm._packetQueue[0])
def test_sendDisconnected(self):
"""
Test send after XML stream disconnection.
The data should be cached until a new XML stream has been established
and initialized.
"""
factory = xmlstream.XmlStreamFactory(xmlstream.Authenticator())
sm = xmlstream.StreamManager(factory)
handler = DummyXMPPHandler()
sm.addHandler(handler)
xs = factory.buildProtocol(None)
xs.connectionMade()
xs.transport = proto_helpers.StringTransport()
xs.connectionLost(None)
sm.send("<presence/>")
self.assertEqual(b"", xs.transport.value())
self.assertEqual("<presence/>", sm._packetQueue[0])
class XmlStreamServerFactoryTests(GenericXmlStreamFactoryTestsMixin):
"""
Tests for L{xmlstream.XmlStreamServerFactory}.
"""
def setUp(self):
"""
Set up a server factory with an authenticator factory function.
"""
class TestAuthenticator(object):
def __init__(self):
self.xmlstreams = []
def associateWithStream(self, xs):
self.xmlstreams.append(xs)
def authenticatorFactory():
return TestAuthenticator()
self.factory = xmlstream.XmlStreamServerFactory(authenticatorFactory)
def test_interface(self):
"""
L{XmlStreamServerFactory} is a L{Factory}.
"""
verifyObject(IProtocolFactory, self.factory)
def test_buildProtocolAuthenticatorInstantiation(self):
"""
The authenticator factory should be used to instantiate the
authenticator and pass it to the protocol.
The default protocol, L{XmlStream} stores the authenticator it is
passed, and calls its C{associateWithStream} method. so we use that to
check whether our authenticator factory is used and the protocol
instance gets an authenticator.
"""
xs = self.factory.buildProtocol(None)
self.assertEqual([xs], xs.authenticator.xmlstreams)
def test_buildProtocolXmlStream(self):
"""
The protocol factory creates Jabber XML Stream protocols by default.
"""
xs = self.factory.buildProtocol(None)
self.assertIsInstance(xs, xmlstream.XmlStream)
def test_buildProtocolTwice(self):
"""
Subsequent calls to buildProtocol should result in different instances
of the protocol, as well as their authenticators.
"""
xs1 = self.factory.buildProtocol(None)
xs2 = self.factory.buildProtocol(None)
self.assertNotIdentical(xs1, xs2)
self.assertNotIdentical(xs1.authenticator, xs2.authenticator)