%PDF- %PDF-
Direktori : /lib/python3/dist-packages/twisted/words/test/ |
Current File : //lib/python3/dist-packages/twisted/words/test/test_jabberxmlstream.py |
# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Tests for L{twisted.words.protocols.jabber.xmlstream}. """ from unittest import skipIf 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.test import proto_helpers from twisted.trial import unittest from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream from twisted.words.test.test_xmlstream import GenericXmlStreamFactoryTestsMixin from twisted.words.xish import domish try: from twisted.internet import ssl as _ssl except ImportError: ssl = None skipWhenNoSSL = (True, "SSL not available") else: ssl = _ssl skipWhenNoSSL = (False, "") from twisted.internet._sslverify import ClientTLSOptions from twisted.internet.ssl import CertificateOptions 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("12345", "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", "secret") def test_passwordNotUnicode(self): """ The password must be a unicode object. """ self.assertRaises(TypeError, xmlstream.hashPassword, "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("12345", "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, str) class TLSInitiatingInitializerTests(unittest.TestCase): def setUp(self): self.output = [] self.done = [] self.savedSSL = xmlstream.ssl self.authenticator = xmlstream.ConnectAuthenticator("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) @skipIf(*skipWhenNoSSL) 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 @skipIf(*skipWhenNoSSL) def test_certificateVerify(self): """ The server certificate will be verified. """ def fakeStartTLS(contextFactory): self.assertIsInstance(contextFactory, ClientTLSOptions) self.assertEqual(contextFactory._hostname, "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 @skipIf(*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 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: """ 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: 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: 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)