Ausgabe der neuen DB Einträge
This commit is contained in:
parent
bad48e1627
commit
cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions
|
|
@ -0,0 +1 @@
|
|||
"Words tests"
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.im.basechat}.
|
||||
"""
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.im import basechat, basesupport
|
||||
|
||||
|
||||
class ChatUITests(unittest.TestCase):
|
||||
"""
|
||||
Tests for the L{basechat.ChatUI} chat client.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.ui = basechat.ChatUI()
|
||||
self.account = basesupport.AbstractAccount("fooAccount", False, "foo",
|
||||
"password", "host", "port")
|
||||
self.person = basesupport.AbstractPerson("foo", self.account)
|
||||
|
||||
|
||||
def test_contactChangedNickNoKey(self):
|
||||
"""
|
||||
L{basechat.ChatUI.contactChangedNick} on an
|
||||
L{twisted.words.im.interfaces.IPerson} who doesn't have an account
|
||||
associated with the L{basechat.ChatUI} instance has no effect.
|
||||
"""
|
||||
self.assertEqual(self.person.name, "foo")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
||||
self.ui.contactChangedNick(self.person, "bar")
|
||||
self.assertEqual(self.person.name, "foo")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
||||
|
||||
def test_contactChangedNickNoConversation(self):
|
||||
"""
|
||||
L{basechat.ChatUI.contactChangedNick} changes the name for an
|
||||
L{twisted.words.im.interfaces.IPerson}.
|
||||
"""
|
||||
self.ui.persons[self.person.name, self.person.account] = self.person
|
||||
|
||||
self.assertEqual(self.person.name, "foo")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
||||
self.ui.contactChangedNick(self.person, "bar")
|
||||
self.assertEqual(self.person.name, "bar")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
||||
|
||||
def test_contactChangedNickHasConversation(self):
|
||||
"""
|
||||
If an L{twisted.words.im.interfaces.IPerson} is in a
|
||||
L{basechat.Conversation}, L{basechat.ChatUI.contactChangedNick} causes a
|
||||
name change for that person in both the L{basechat.Conversation} and the
|
||||
L{basechat.ChatUI}.
|
||||
"""
|
||||
self.ui.persons[self.person.name, self.person.account] = self.person
|
||||
conversation = basechat.Conversation(self.person, self.ui)
|
||||
self.ui.conversations[self.person] = conversation
|
||||
|
||||
self.assertEqual(self.person.name, "foo")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
||||
self.ui.contactChangedNick(self.person, "bar")
|
||||
self.assertEqual(self.person.name, "bar")
|
||||
self.assertEqual(self.person.account, self.account)
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.im import basesupport
|
||||
from twisted.internet import error, defer
|
||||
|
||||
class DummyAccount(basesupport.AbstractAccount):
|
||||
"""
|
||||
An account object that will do nothing when asked to start to log on.
|
||||
"""
|
||||
|
||||
loginHasFailed = False
|
||||
loginCallbackCalled = False
|
||||
|
||||
def _startLogOn(self, *args):
|
||||
"""
|
||||
Set self.loginDeferred to the same as the deferred returned, allowing a
|
||||
testcase to .callback or .errback.
|
||||
|
||||
@return: A deferred.
|
||||
"""
|
||||
self.loginDeferred = defer.Deferred()
|
||||
return self.loginDeferred
|
||||
|
||||
def _loginFailed(self, result):
|
||||
self.loginHasFailed = True
|
||||
return basesupport.AbstractAccount._loginFailed(self, result)
|
||||
|
||||
def _cb_logOn(self, result):
|
||||
self.loginCallbackCalled = True
|
||||
return basesupport.AbstractAccount._cb_logOn(self, result)
|
||||
|
||||
class DummyUI(object):
|
||||
"""
|
||||
Provide just the interface required to be passed to AbstractAccount.logOn.
|
||||
"""
|
||||
clientRegistered = False
|
||||
|
||||
def registerAccountClient(self, result):
|
||||
self.clientRegistered = True
|
||||
|
||||
class ClientMsgTests(unittest.TestCase):
|
||||
def makeUI(self):
|
||||
return DummyUI()
|
||||
|
||||
def makeAccount(self):
|
||||
return DummyAccount('la', False, 'la', None, 'localhost', 6667)
|
||||
|
||||
def test_connect(self):
|
||||
"""
|
||||
Test that account.logOn works, and it calls the right callback when a
|
||||
connection is established.
|
||||
"""
|
||||
account = self.makeAccount()
|
||||
ui = self.makeUI()
|
||||
d = account.logOn(ui)
|
||||
account.loginDeferred.callback(None)
|
||||
|
||||
def check(result):
|
||||
self.assertFalse(account.loginHasFailed,
|
||||
"Login shouldn't have failed")
|
||||
self.assertTrue(account.loginCallbackCalled,
|
||||
"We should be logged in")
|
||||
d.addCallback(check)
|
||||
return d
|
||||
|
||||
def test_failedConnect(self):
|
||||
"""
|
||||
Test that account.logOn works, and it calls the right callback when a
|
||||
connection is established.
|
||||
"""
|
||||
account = self.makeAccount()
|
||||
ui = self.makeUI()
|
||||
d = account.logOn(ui)
|
||||
account.loginDeferred.errback(Exception())
|
||||
|
||||
def err(reason):
|
||||
self.assertTrue(account.loginHasFailed, "Login should have failed")
|
||||
self.assertFalse(account.loginCallbackCalled,
|
||||
"We shouldn't be logged in")
|
||||
self.assertTrue(not ui.clientRegistered,
|
||||
"Client shouldn't be registered in the UI")
|
||||
cb = lambda r: self.assertTrue(False, "Shouldn't get called back")
|
||||
d.addCallbacks(cb, err)
|
||||
return d
|
||||
|
||||
def test_alreadyConnecting(self):
|
||||
"""
|
||||
Test that it can fail sensibly when someone tried to connect before
|
||||
we did.
|
||||
"""
|
||||
account = self.makeAccount()
|
||||
ui = self.makeUI()
|
||||
account.logOn(ui)
|
||||
self.assertRaises(error.ConnectError, account.logOn, ui)
|
||||
|
||||
|
|
@ -0,0 +1,587 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.xish.domish}, a DOM-like library for XMPP.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from zope.interface.verify import verifyObject
|
||||
|
||||
from twisted.python.compat import _PY3, unicode
|
||||
from twisted.python.reflect import requireModule
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.xish import domish
|
||||
|
||||
|
||||
class ElementTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{domish.Element}.
|
||||
"""
|
||||
|
||||
def test_interface(self):
|
||||
"""
|
||||
L{domish.Element} implements L{domish.IElement}.
|
||||
"""
|
||||
verifyObject(domish.IElement, domish.Element((None, u"foo")))
|
||||
|
||||
|
||||
def test_escaping(self):
|
||||
"""
|
||||
The built-in entity references are properly encoded.
|
||||
"""
|
||||
s = "&<>'\""
|
||||
self.assertEqual(domish.escapeToXml(s), "&<>'\"")
|
||||
self.assertEqual(domish.escapeToXml(s, 1), "&<>'"")
|
||||
|
||||
|
||||
def test_namespace(self):
|
||||
"""
|
||||
An attribute on L{domish.Namespace} yields a qualified name.
|
||||
"""
|
||||
ns = domish.Namespace("testns")
|
||||
self.assertEqual(ns.foo, ("testns", "foo"))
|
||||
|
||||
|
||||
def test_elementInit(self):
|
||||
"""
|
||||
Basic L{domish.Element} initialization tests.
|
||||
"""
|
||||
e = domish.Element((None, "foo"))
|
||||
self.assertEqual(e.name, "foo")
|
||||
self.assertEqual(e.uri, None)
|
||||
self.assertEqual(e.defaultUri, None)
|
||||
self.assertEqual(e.parent, None)
|
||||
|
||||
e = domish.Element(("", "foo"))
|
||||
self.assertEqual(e.name, "foo")
|
||||
self.assertEqual(e.uri, "")
|
||||
self.assertEqual(e.defaultUri, "")
|
||||
self.assertEqual(e.parent, None)
|
||||
|
||||
e = domish.Element(("testns", "foo"))
|
||||
self.assertEqual(e.name, "foo")
|
||||
self.assertEqual(e.uri, "testns")
|
||||
self.assertEqual(e.defaultUri, "testns")
|
||||
self.assertEqual(e.parent, None)
|
||||
|
||||
e = domish.Element(("testns", "foo"), "test2ns")
|
||||
self.assertEqual(e.name, "foo")
|
||||
self.assertEqual(e.uri, "testns")
|
||||
self.assertEqual(e.defaultUri, "test2ns")
|
||||
|
||||
|
||||
def test_childOps(self):
|
||||
"""
|
||||
Basic L{domish.Element} child tests.
|
||||
"""
|
||||
e = domish.Element(("testns", "foo"))
|
||||
e.addContent(u"somecontent")
|
||||
b2 = e.addElement(("testns2", "bar2"))
|
||||
e["attrib1"] = "value1"
|
||||
e[("testns2", "attrib2")] = "value2"
|
||||
e.addElement("bar")
|
||||
e.addElement("bar")
|
||||
e.addContent(u"abc")
|
||||
e.addContent(u"123")
|
||||
|
||||
# Check content merging
|
||||
self.assertEqual(e.children[-1], "abc123")
|
||||
|
||||
# Check direct child accessor
|
||||
self.assertEqual(e.bar2, b2)
|
||||
e.bar2.addContent(u"subcontent")
|
||||
e.bar2["bar2value"] = "somevalue"
|
||||
|
||||
# Check child ops
|
||||
self.assertEqual(e.children[1], e.bar2)
|
||||
self.assertEqual(e.children[2], e.bar)
|
||||
|
||||
# Check attribute ops
|
||||
self.assertEqual(e["attrib1"], "value1")
|
||||
del e["attrib1"]
|
||||
self.assertEqual(e.hasAttribute("attrib1"), 0)
|
||||
self.assertEqual(e.hasAttribute("attrib2"), 0)
|
||||
self.assertEqual(e[("testns2", "attrib2")], "value2")
|
||||
|
||||
|
||||
def test_characterData(self):
|
||||
"""
|
||||
Extract character data using L{unicode}.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent(u"somecontent")
|
||||
|
||||
text = unicode(element)
|
||||
self.assertEqual(u"somecontent", text)
|
||||
self.assertIsInstance(text, unicode)
|
||||
|
||||
|
||||
def test_characterDataNativeString(self):
|
||||
"""
|
||||
Extract ascii character data using L{str}.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent(u"somecontent")
|
||||
|
||||
text = str(element)
|
||||
self.assertEqual("somecontent", text)
|
||||
self.assertIsInstance(text, str)
|
||||
|
||||
|
||||
def test_characterDataUnicode(self):
|
||||
"""
|
||||
Extract character data using L{unicode}.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent(u"\N{SNOWMAN}")
|
||||
|
||||
text = unicode(element)
|
||||
self.assertEqual(u"\N{SNOWMAN}", text)
|
||||
self.assertIsInstance(text, unicode)
|
||||
|
||||
|
||||
def test_characterDataBytes(self):
|
||||
"""
|
||||
Extract character data as UTF-8 using L{bytes}.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent(u"\N{SNOWMAN}")
|
||||
|
||||
text = bytes(element)
|
||||
self.assertEqual(u"\N{SNOWMAN}".encode('utf-8'), text)
|
||||
self.assertIsInstance(text, bytes)
|
||||
|
||||
|
||||
def test_characterDataMixed(self):
|
||||
"""
|
||||
Mixing addChild with cdata and element, the first cdata is returned.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addChild(u"abc")
|
||||
element.addElement("bar")
|
||||
element.addChild(u"def")
|
||||
self.assertEqual(u"abc", unicode(element))
|
||||
|
||||
|
||||
def test_addContent(self):
|
||||
"""
|
||||
Unicode strings passed to C{addContent} become the character data.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent(u'unicode')
|
||||
self.assertEqual(u"unicode", unicode(element))
|
||||
|
||||
|
||||
def test_addContentNativeStringASCII(self):
|
||||
"""
|
||||
ASCII native strings passed to C{addContent} become the character data.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
element.addContent('native')
|
||||
self.assertEqual(u"native", unicode(element))
|
||||
|
||||
|
||||
def test_addContentBytes(self):
|
||||
"""
|
||||
Byte strings passed to C{addContent} are not acceptable on Python 3.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
self.assertRaises(TypeError, element.addContent, b'bytes')
|
||||
if not _PY3:
|
||||
test_addContentBytes.skip = (
|
||||
"Bytes behavior of addContent only provided on Python 3.")
|
||||
|
||||
|
||||
def test_addContentBytesNonASCII(self):
|
||||
"""
|
||||
Non-ASCII byte strings passed to C{addContent} yield L{UnicodeError}.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
self.assertRaises(UnicodeError, element.addContent, b'\xe2\x98\x83')
|
||||
if _PY3:
|
||||
test_addContentBytesNonASCII.skip = (
|
||||
"Bytes behavior of addContent only provided on Python 2.")
|
||||
|
||||
|
||||
def test_addElementContent(self):
|
||||
"""
|
||||
Content passed to addElement becomes character data on the new child.
|
||||
"""
|
||||
element = domish.Element((u"testns", u"foo"))
|
||||
child = element.addElement("bar", content=u"abc")
|
||||
self.assertEqual(u"abc", unicode(child))
|
||||
|
||||
|
||||
def test_elements(self):
|
||||
"""
|
||||
Calling C{elements} without arguments on a L{domish.Element} returns
|
||||
all child elements, whatever the qualified name.
|
||||
"""
|
||||
e = domish.Element((u"testns", u"foo"))
|
||||
c1 = e.addElement(u"name")
|
||||
c2 = e.addElement((u"testns2", u"baz"))
|
||||
c3 = e.addElement(u"quux")
|
||||
c4 = e.addElement((u"testns", u"name"))
|
||||
|
||||
elts = list(e.elements())
|
||||
|
||||
self.assertIn(c1, elts)
|
||||
self.assertIn(c2, elts)
|
||||
self.assertIn(c3, elts)
|
||||
self.assertIn(c4, elts)
|
||||
|
||||
|
||||
def test_elementsWithQN(self):
|
||||
"""
|
||||
Calling C{elements} with a namespace and local name on a
|
||||
L{domish.Element} returns all child elements with that qualified name.
|
||||
"""
|
||||
e = domish.Element((u"testns", u"foo"))
|
||||
c1 = e.addElement(u"name")
|
||||
c2 = e.addElement((u"testns2", u"baz"))
|
||||
c3 = e.addElement(u"quux")
|
||||
c4 = e.addElement((u"testns", u"name"))
|
||||
|
||||
elts = list(e.elements(u"testns", u"name"))
|
||||
|
||||
self.assertIn(c1, elts)
|
||||
self.assertNotIn(c2, elts)
|
||||
self.assertNotIn(c3, elts)
|
||||
self.assertIn(c4, elts)
|
||||
|
||||
|
||||
|
||||
class DomishStreamTestsMixin:
|
||||
"""
|
||||
Mixin defining tests for different stream implementations.
|
||||
|
||||
@ivar streamClass: A no-argument callable which will be used to create an
|
||||
XML parser which can produce a stream of elements from incremental
|
||||
input.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.doc_started = False
|
||||
self.doc_ended = False
|
||||
self.root = None
|
||||
self.elements = []
|
||||
self.stream = self.streamClass()
|
||||
self.stream.DocumentStartEvent = self._docStarted
|
||||
self.stream.ElementEvent = self.elements.append
|
||||
self.stream.DocumentEndEvent = self._docEnded
|
||||
|
||||
def _docStarted(self, root):
|
||||
self.root = root
|
||||
self.doc_started = True
|
||||
|
||||
def _docEnded(self):
|
||||
self.doc_ended = True
|
||||
|
||||
def doTest(self, xml):
|
||||
self.stream.parse(xml)
|
||||
|
||||
def testHarness(self):
|
||||
xml = b"<root><child/><child2/></root>"
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.doc_started, True)
|
||||
self.assertEqual(self.root.name, 'root')
|
||||
self.assertEqual(self.elements[0].name, 'child')
|
||||
self.assertEqual(self.elements[1].name, 'child2')
|
||||
self.assertEqual(self.doc_ended, True)
|
||||
|
||||
def testBasic(self):
|
||||
xml = b"<stream:stream xmlns:stream='etherx' xmlns='jabber'>\n" + \
|
||||
b" <message to='bar'>" + \
|
||||
b" <x xmlns='xdelay'>some&data></x>" + \
|
||||
b" </message>" + \
|
||||
b"</stream:stream>"
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.root.name, 'stream')
|
||||
self.assertEqual(self.root.uri, 'etherx')
|
||||
self.assertEqual(self.elements[0].name, 'message')
|
||||
self.assertEqual(self.elements[0].uri, 'jabber')
|
||||
self.assertEqual(self.elements[0]['to'], 'bar')
|
||||
self.assertEqual(self.elements[0].x.uri, 'xdelay')
|
||||
self.assertEqual(unicode(self.elements[0].x), 'some&data>')
|
||||
|
||||
def testNoRootNS(self):
|
||||
xml = b"<stream><error xmlns='etherx'/></stream>"
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.root.uri, '')
|
||||
self.assertEqual(self.elements[0].uri, 'etherx')
|
||||
|
||||
def testNoDefaultNS(self):
|
||||
xml = b"<stream:stream xmlns:stream='etherx'><error/></stream:stream>"
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.root.uri, 'etherx')
|
||||
self.assertEqual(self.root.defaultUri, '')
|
||||
self.assertEqual(self.elements[0].uri, '')
|
||||
self.assertEqual(self.elements[0].defaultUri, '')
|
||||
|
||||
def testChildDefaultNS(self):
|
||||
xml = b"<root xmlns='testns'><child/></root>"
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.root.uri, 'testns')
|
||||
self.assertEqual(self.elements[0].uri, 'testns')
|
||||
|
||||
def testEmptyChildNS(self):
|
||||
xml = b"""<root xmlns='testns'>
|
||||
<child1><child2 xmlns=''/></child1>
|
||||
</root>"""
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.elements[0].child2.uri, '')
|
||||
|
||||
|
||||
def test_namespaceWithWhitespace(self):
|
||||
"""
|
||||
Whitespace in an xmlns value is preserved in the resulting node's C{uri}
|
||||
attribute.
|
||||
"""
|
||||
xml = b"<root xmlns:foo=' bar baz '><foo:bar foo:baz='quux'/></root>"
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.elements[0].uri, " bar baz ")
|
||||
self.assertEqual(
|
||||
self.elements[0].attributes, {(" bar baz ", "baz"): "quux"})
|
||||
|
||||
|
||||
def test_attributesWithNamespaces(self):
|
||||
"""
|
||||
Attributes with namespace are parsed without Exception.
|
||||
(https://twistedmatrix.com/trac/ticket/9730 regression test)
|
||||
"""
|
||||
|
||||
xml = b"""<root xmlns:test='http://example.org' xml:lang='en'>
|
||||
<test:test>test</test:test>
|
||||
</root>"""
|
||||
|
||||
# with Python 3.8 and without #9730 fix, the following error would
|
||||
# happen at next line:
|
||||
# ``RuntimeError: dictionary keys changed during iteration``
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.elements[0].uri, "http://example.org")
|
||||
|
||||
|
||||
def testChildPrefix(self):
|
||||
xml = b"<root xmlns='testns' xmlns:foo='testns2'><foo:child/></root>"
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual(self.root.localPrefixes['foo'], 'testns2')
|
||||
self.assertEqual(self.elements[0].uri, 'testns2')
|
||||
|
||||
def testUnclosedElement(self):
|
||||
self.assertRaises(domish.ParserError, self.stream.parse,
|
||||
b"<root><error></root>")
|
||||
|
||||
def test_namespaceReuse(self):
|
||||
"""
|
||||
Test that reuse of namespaces does affect an element's serialization.
|
||||
|
||||
When one element uses a prefix for a certain namespace, this is
|
||||
stored in the C{localPrefixes} attribute of the element. We want
|
||||
to make sure that elements created after such use, won't have this
|
||||
prefix end up in their C{localPrefixes} attribute, too.
|
||||
"""
|
||||
|
||||
xml = b"""<root>
|
||||
<foo:child1 xmlns:foo='testns'/>
|
||||
<child2 xmlns='testns'/>
|
||||
</root>"""
|
||||
|
||||
self.stream.parse(xml)
|
||||
self.assertEqual('child1', self.elements[0].name)
|
||||
self.assertEqual('testns', self.elements[0].uri)
|
||||
self.assertEqual('', self.elements[0].defaultUri)
|
||||
self.assertEqual({'foo': 'testns'}, self.elements[0].localPrefixes)
|
||||
self.assertEqual('child2', self.elements[1].name)
|
||||
self.assertEqual('testns', self.elements[1].uri)
|
||||
self.assertEqual('testns', self.elements[1].defaultUri)
|
||||
self.assertEqual({}, self.elements[1].localPrefixes)
|
||||
|
||||
|
||||
|
||||
class DomishExpatStreamTests(DomishStreamTestsMixin, unittest.TestCase):
|
||||
"""
|
||||
Tests for L{domish.ExpatElementStream}, the expat-based element stream
|
||||
implementation.
|
||||
"""
|
||||
streamClass = domish.ExpatElementStream
|
||||
|
||||
if requireModule('pyexpat', default=None) is None:
|
||||
skip = "pyexpat is required for ExpatElementStream tests."
|
||||
else:
|
||||
skip = None
|
||||
|
||||
|
||||
|
||||
class DomishSuxStreamTests(DomishStreamTestsMixin, unittest.TestCase):
|
||||
"""
|
||||
Tests for L{domish.SuxElementStream}, the L{twisted.web.sux}-based element
|
||||
stream implementation.
|
||||
"""
|
||||
streamClass = domish.SuxElementStream
|
||||
|
||||
|
||||
|
||||
class SerializerTests(unittest.TestCase):
|
||||
def testNoNamespace(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
self.assertEqual(e.toXml(), "<foo/>")
|
||||
self.assertEqual(e.toXml(closeElement = 0), "<foo>")
|
||||
|
||||
def testDefaultNamespace(self):
|
||||
e = domish.Element(("testns", "foo"))
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns'/>")
|
||||
|
||||
def testOtherNamespace(self):
|
||||
e = domish.Element(("testns", "foo"), "testns2")
|
||||
self.assertEqual(e.toXml({'testns': 'bar'}),
|
||||
"<bar:foo xmlns:bar='testns' xmlns='testns2'/>")
|
||||
|
||||
def testChildDefaultNamespace(self):
|
||||
e = domish.Element(("testns", "foo"))
|
||||
e.addElement("bar")
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
|
||||
|
||||
def testChildSameNamespace(self):
|
||||
e = domish.Element(("testns", "foo"))
|
||||
e.addElement(("testns", "bar"))
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
|
||||
|
||||
def testChildSameDefaultNamespace(self):
|
||||
e = domish.Element(("testns", "foo"))
|
||||
e.addElement("bar", "testns")
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar/></foo>")
|
||||
|
||||
def testChildOtherDefaultNamespace(self):
|
||||
e = domish.Element(("testns", "foo"))
|
||||
e.addElement(("testns2", "bar"), 'testns2')
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns'><bar xmlns='testns2'/></foo>")
|
||||
|
||||
def testOnlyChildDefaultNamespace(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
e.addElement(("ns2", "bar"), 'ns2')
|
||||
self.assertEqual(e.toXml(), "<foo><bar xmlns='ns2'/></foo>")
|
||||
|
||||
def testOnlyChildDefaultNamespace2(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
e.addElement("bar")
|
||||
self.assertEqual(e.toXml(), "<foo><bar/></foo>")
|
||||
|
||||
def testChildInDefaultNamespace(self):
|
||||
e = domish.Element(("testns", "foo"), "testns2")
|
||||
e.addElement(("testns2", "bar"))
|
||||
self.assertEqual(e.toXml(), "<xn0:foo xmlns:xn0='testns' xmlns='testns2'><bar/></xn0:foo>")
|
||||
|
||||
def testQualifiedAttribute(self):
|
||||
e = domish.Element((None, "foo"),
|
||||
attribs = {("testns2", "bar"): "baz"})
|
||||
self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'/>")
|
||||
|
||||
def testQualifiedAttributeDefaultNS(self):
|
||||
e = domish.Element(("testns", "foo"),
|
||||
attribs = {("testns", "bar"): "baz"})
|
||||
self.assertEqual(e.toXml(), "<foo xmlns='testns' xmlns:xn0='testns' xn0:bar='baz'/>")
|
||||
|
||||
def testTwoChilds(self):
|
||||
e = domish.Element(('', "foo"))
|
||||
child1 = e.addElement(("testns", "bar"), "testns2")
|
||||
child1.addElement(('testns2', 'quux'))
|
||||
child2 = e.addElement(("testns3", "baz"), "testns4")
|
||||
child2.addElement(('testns', 'quux'))
|
||||
self.assertEqual(e.toXml(), "<foo><xn0:bar xmlns:xn0='testns' xmlns='testns2'><quux/></xn0:bar><xn1:baz xmlns:xn1='testns3' xmlns='testns4'><xn0:quux xmlns:xn0='testns'/></xn1:baz></foo>")
|
||||
|
||||
def testXMLNamespace(self):
|
||||
e = domish.Element((None, "foo"),
|
||||
attribs = {("http://www.w3.org/XML/1998/namespace",
|
||||
"lang"): "en_US"})
|
||||
self.assertEqual(e.toXml(), "<foo xml:lang='en_US'/>")
|
||||
|
||||
def testQualifiedAttributeGivenListOfPrefixes(self):
|
||||
e = domish.Element((None, "foo"),
|
||||
attribs = {("testns2", "bar"): "baz"})
|
||||
self.assertEqual(e.toXml({"testns2": "qux"}),
|
||||
"<foo xmlns:qux='testns2' qux:bar='baz'/>")
|
||||
|
||||
def testNSPrefix(self):
|
||||
e = domish.Element((None, "foo"),
|
||||
attribs = {("testns2", "bar"): "baz"})
|
||||
c = e.addElement(("testns2", "qux"))
|
||||
c[("testns2", "bar")] = "quux"
|
||||
|
||||
self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'/></foo>")
|
||||
|
||||
def testDefaultNSPrefix(self):
|
||||
e = domish.Element((None, "foo"),
|
||||
attribs = {("testns2", "bar"): "baz"})
|
||||
c = e.addElement(("testns2", "qux"))
|
||||
c[("testns2", "bar")] = "quux"
|
||||
c.addElement('foo')
|
||||
|
||||
self.assertEqual(e.toXml(), "<foo xmlns:xn0='testns2' xn0:bar='baz'><xn0:qux xn0:bar='quux'><xn0:foo/></xn0:qux></foo>")
|
||||
|
||||
def testPrefixScope(self):
|
||||
e = domish.Element(('testns', 'foo'))
|
||||
|
||||
self.assertEqual(e.toXml(prefixes={'testns': 'bar'},
|
||||
prefixesInScope=['bar']),
|
||||
"<bar:foo/>")
|
||||
|
||||
def testLocalPrefixes(self):
|
||||
e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
|
||||
self.assertEqual(e.toXml(), "<bar:foo xmlns:bar='testns'/>")
|
||||
|
||||
def testLocalPrefixesWithChild(self):
|
||||
e = domish.Element(('testns', 'foo'), localPrefixes={'bar': 'testns'})
|
||||
e.addElement('baz')
|
||||
self.assertIdentical(e.baz.defaultUri, None)
|
||||
self.assertEqual(e.toXml(), "<bar:foo xmlns:bar='testns'><baz/></bar:foo>")
|
||||
|
||||
def test_prefixesReuse(self):
|
||||
"""
|
||||
Test that prefixes passed to serialization are not modified.
|
||||
|
||||
This test makes sure that passing a dictionary of prefixes repeatedly
|
||||
to C{toXml} of elements does not cause serialization errors. A
|
||||
previous implementation changed the passed in dictionary internally,
|
||||
causing havoc later on.
|
||||
"""
|
||||
prefixes = {'testns': 'foo'}
|
||||
|
||||
# test passing of dictionary
|
||||
s = domish.SerializerClass(prefixes=prefixes)
|
||||
self.assertNotIdentical(prefixes, s.prefixes)
|
||||
|
||||
# test proper serialization on prefixes reuse
|
||||
e = domish.Element(('testns2', 'foo'),
|
||||
localPrefixes={'quux': 'testns2'})
|
||||
self.assertEqual("<quux:foo xmlns:quux='testns2'/>",
|
||||
e.toXml(prefixes=prefixes))
|
||||
e = domish.Element(('testns2', 'foo'))
|
||||
self.assertEqual("<foo xmlns='testns2'/>",
|
||||
e.toXml(prefixes=prefixes))
|
||||
|
||||
def testRawXMLSerialization(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
e.addRawXml("<abc123>")
|
||||
# The testcase below should NOT generate valid XML -- that's
|
||||
# the whole point of using the raw XML call -- it's the callers
|
||||
# responsibility to ensure that the data inserted is valid
|
||||
self.assertEqual(e.toXml(), "<foo><abc123></foo>")
|
||||
|
||||
def testRawXMLWithUnicodeSerialization(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
e.addRawXml(u"<degree>\u00B0</degree>")
|
||||
self.assertEqual(e.toXml(), u"<foo><degree>\u00B0</degree></foo>")
|
||||
|
||||
def testUnicodeSerialization(self):
|
||||
e = domish.Element((None, "foo"))
|
||||
e["test"] = u"my value\u0221e"
|
||||
e.addContent(u"A degree symbol...\u00B0")
|
||||
self.assertEqual(e.toXml(),
|
||||
u"<foo test='my value\u0221e'>A degree symbol...\u00B0</foo>")
|
||||
2978
venv/lib/python3.9/site-packages/twisted/words/test/test_irc.py
Normal file
2978
venv/lib/python3.9/site-packages/twisted/words/test/test_irc.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,291 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for IRC portions of L{twisted.words.service}.
|
||||
"""
|
||||
|
||||
from twisted.cred import checkers, portal
|
||||
from twisted.test import proto_helpers
|
||||
from twisted.words.protocols import irc
|
||||
from twisted.words.service import InMemoryWordsRealm, IRCFactory, IRCUser
|
||||
from twisted.words.test.test_irc import IRCTestCase
|
||||
|
||||
class IRCUserTests(IRCTestCase):
|
||||
"""
|
||||
Isolated tests for L{IRCUser}
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Sets up a Realm, Portal, Factory, IRCUser, Transport, and Connection
|
||||
for our tests.
|
||||
"""
|
||||
self.realm = InMemoryWordsRealm("example.com")
|
||||
self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
|
||||
self.portal = portal.Portal(self.realm, [self.checker])
|
||||
self.checker.addUser(u"john", u"pass")
|
||||
self.factory = IRCFactory(self.realm, self.portal)
|
||||
self.ircUser = self.factory.buildProtocol(None)
|
||||
self.stringTransport = proto_helpers.StringTransport()
|
||||
self.ircUser.makeConnection(self.stringTransport)
|
||||
|
||||
|
||||
def test_sendMessage(self):
|
||||
"""
|
||||
Sending a message to a user after they have sent NICK, but before they
|
||||
have authenticated, results in a message from "example.com".
|
||||
"""
|
||||
self.ircUser.irc_NICK("", ["mynick"])
|
||||
self.stringTransport.clear()
|
||||
self.ircUser.sendMessage("foo")
|
||||
self.assertEqualBufferValue(self.stringTransport.value(), ":example.com foo mynick\r\n")
|
||||
|
||||
|
||||
def test_utf8Messages(self):
|
||||
"""
|
||||
When a UTF8 message is sent with sendMessage and the current IRCUser
|
||||
has a UTF8 nick and is set to UTF8 encoding, the message will be
|
||||
written to the transport.
|
||||
"""
|
||||
expectedResult = (u":example.com \u0442\u0435\u0441\u0442 "
|
||||
u"\u043d\u0438\u043a\r\n").encode('utf-8')
|
||||
|
||||
self.ircUser.irc_NICK("", [u"\u043d\u0438\u043a".encode('utf-8')])
|
||||
self.stringTransport.clear()
|
||||
self.ircUser.sendMessage(u"\u0442\u0435\u0441\u0442".encode('utf-8'))
|
||||
self.assertEqualBufferValue(self.stringTransport.value(), expectedResult)
|
||||
|
||||
|
||||
def test_invalidEncodingNick(self):
|
||||
"""
|
||||
A NICK command sent with a nickname that cannot be decoded with the
|
||||
current IRCUser's encoding results in a PRIVMSG from NickServ
|
||||
indicating that the nickname could not be decoded.
|
||||
"""
|
||||
self.ircUser.irc_NICK("", [b"\xd4\xc5\xd3\xd4"])
|
||||
self.assertRaises(UnicodeError)
|
||||
|
||||
|
||||
def response(self):
|
||||
"""
|
||||
Grabs our responses and then clears the transport
|
||||
"""
|
||||
response = self.ircUser.transport.value()
|
||||
self.ircUser.transport.clear()
|
||||
if bytes != str and isinstance(response, bytes):
|
||||
response = response.decode("utf-8")
|
||||
response = response.splitlines()
|
||||
return [irc.parsemsg(r) for r in response]
|
||||
|
||||
|
||||
def scanResponse(self, response, messageType):
|
||||
"""
|
||||
Gets messages out of a response
|
||||
|
||||
@param response: The parsed IRC messages of the response, as returned
|
||||
by L{IRCUserTests.response}
|
||||
|
||||
@param messageType: The string type of the desired messages.
|
||||
|
||||
@return: An iterator which yields 2-tuples of C{(index, ircMessage)}
|
||||
"""
|
||||
for n, message in enumerate(response):
|
||||
if (message[1] == messageType):
|
||||
yield n, message
|
||||
|
||||
|
||||
def test_sendNickSendsGreeting(self):
|
||||
"""
|
||||
Receiving NICK without authenticating sends the MOTD Start and MOTD End
|
||||
messages, which is required by certain popular IRC clients (such as
|
||||
Pidgin) before a connection is considered to be fully established.
|
||||
"""
|
||||
self.ircUser.irc_NICK("", ["mynick"])
|
||||
response = self.response()
|
||||
start = list(self.scanResponse(response, irc.RPL_MOTDSTART))
|
||||
end = list(self.scanResponse(response, irc.RPL_ENDOFMOTD))
|
||||
self.assertEqual(start,
|
||||
[(0, ('example.com', '375', ['mynick', '- example.com Message of the Day - ']))])
|
||||
self.assertEqual(end,
|
||||
[(1, ('example.com', '376', ['mynick', 'End of /MOTD command.']))])
|
||||
|
||||
|
||||
def test_fullLogin(self):
|
||||
"""
|
||||
Receiving USER, PASS, NICK will log in the user, and transmit the
|
||||
appropriate response messages.
|
||||
"""
|
||||
self.ircUser.irc_USER("", ["john doe"])
|
||||
self.ircUser.irc_PASS("", ["pass"])
|
||||
self.ircUser.irc_NICK("", ["john"])
|
||||
|
||||
version = ('Your host is example.com, running version %s' %
|
||||
(self.factory._serverInfo["serviceVersion"],))
|
||||
|
||||
creation = ('This server was created on %s' %
|
||||
(self.factory._serverInfo["creationDate"],))
|
||||
|
||||
self.assertEqual(self.response(),
|
||||
[('example.com', '375',
|
||||
['john', '- example.com Message of the Day - ']),
|
||||
('example.com', '376', ['john', 'End of /MOTD command.']),
|
||||
('example.com', '001', ['john', 'connected to Twisted IRC']),
|
||||
('example.com', '002', ['john', version]),
|
||||
('example.com', '003', ['john', creation]),
|
||||
('example.com', '004',
|
||||
['john', 'example.com', self.factory._serverInfo["serviceVersion"],
|
||||
'w', 'n'])])
|
||||
|
||||
|
||||
def test_PART(self):
|
||||
"""
|
||||
irc_PART
|
||||
"""
|
||||
self.ircUser.irc_NICK("testuser", ["mynick"])
|
||||
response = self.response()
|
||||
self.ircUser.transport.clear()
|
||||
self.assertEqual(response[0][1], irc.RPL_MOTDSTART)
|
||||
self.ircUser.irc_JOIN("testuser", ["somechannel"])
|
||||
response = self.response()
|
||||
self.ircUser.transport.clear()
|
||||
self.assertEqual(response[0][1], irc.ERR_NOSUCHCHANNEL)
|
||||
self.ircUser.irc_PART("testuser", [b"somechannel", b"booga"])
|
||||
response = self.response()
|
||||
self.ircUser.transport.clear()
|
||||
self.assertEqual(response[0][1], irc.ERR_NOTONCHANNEL)
|
||||
self.ircUser.irc_PART("testuser", [u"somechannel", u"booga"])
|
||||
response = self.response()
|
||||
self.ircUser.transport.clear()
|
||||
self.assertEqual(response[0][1], irc.ERR_NOTONCHANNEL)
|
||||
|
||||
|
||||
def test_NAMES(self):
|
||||
"""
|
||||
irc_NAMES
|
||||
"""
|
||||
self.ircUser.irc_NICK("", ["testuser"])
|
||||
self.ircUser.irc_JOIN("", ["somechannel"])
|
||||
self.ircUser.transport.clear()
|
||||
self.ircUser.irc_NAMES("", ["somechannel"])
|
||||
response = self.response()
|
||||
self.assertEqual(response[0][1], irc.RPL_ENDOFNAMES)
|
||||
|
||||
|
||||
|
||||
class MocksyIRCUser(IRCUser):
|
||||
def __init__(self):
|
||||
self.realm = InMemoryWordsRealm("example.com")
|
||||
self.mockedCodes = []
|
||||
|
||||
|
||||
def sendMessage(self, code, *_, **__):
|
||||
self.mockedCodes.append(code)
|
||||
|
||||
|
||||
BADTEXT = b'\xff'
|
||||
|
||||
class IRCUserBadEncodingTests(IRCTestCase):
|
||||
"""
|
||||
Verifies that L{IRCUser} sends the correct error messages back to clients
|
||||
when given indecipherable bytes
|
||||
"""
|
||||
# TODO: irc_NICK -- but NICKSERV is used for that, so it isn't as easy.
|
||||
|
||||
def setUp(self):
|
||||
self.ircUser = MocksyIRCUser()
|
||||
|
||||
|
||||
def assertChokesOnBadBytes(self, irc_x, error):
|
||||
"""
|
||||
Asserts that IRCUser sends the relevant error code when a given irc_x
|
||||
dispatch method is given undecodable bytes.
|
||||
|
||||
@param irc_x: the name of the irc_FOO method to test.
|
||||
For example, irc_x = 'PRIVMSG' will check irc_PRIVMSG
|
||||
|
||||
@param error: the error code irc_x should send. For example,
|
||||
irc.ERR_NOTONCHANNEL
|
||||
"""
|
||||
getattr(self.ircUser, 'irc_%s' % irc_x)(None, [BADTEXT])
|
||||
self.assertEqual(self.ircUser.mockedCodes, [error])
|
||||
|
||||
# No such channel
|
||||
|
||||
|
||||
def test_JOIN(self):
|
||||
"""
|
||||
Tests that irc_JOIN sends ERR_NOSUCHCHANNEL if the channel name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('JOIN', irc.ERR_NOSUCHCHANNEL)
|
||||
|
||||
|
||||
def test_NAMES(self):
|
||||
"""
|
||||
Tests that irc_NAMES sends ERR_NOSUCHCHANNEL if the channel name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('NAMES', irc.ERR_NOSUCHCHANNEL)
|
||||
|
||||
|
||||
def test_TOPIC(self):
|
||||
"""
|
||||
Tests that irc_TOPIC sends ERR_NOSUCHCHANNEL if the channel name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('TOPIC', irc.ERR_NOSUCHCHANNEL)
|
||||
|
||||
|
||||
def test_LIST(self):
|
||||
"""
|
||||
Tests that irc_LIST sends ERR_NOSUCHCHANNEL if the channel name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('LIST', irc.ERR_NOSUCHCHANNEL)
|
||||
|
||||
# No such nick
|
||||
|
||||
|
||||
def test_MODE(self):
|
||||
"""
|
||||
Tests that irc_MODE sends ERR_NOSUCHNICK if the target name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('MODE', irc.ERR_NOSUCHNICK)
|
||||
|
||||
|
||||
def test_PRIVMSG(self):
|
||||
"""
|
||||
Tests that irc_PRIVMSG sends ERR_NOSUCHNICK if the target name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('PRIVMSG', irc.ERR_NOSUCHNICK)
|
||||
|
||||
|
||||
def test_WHOIS(self):
|
||||
"""
|
||||
Tests that irc_WHOIS sends ERR_NOSUCHNICK if the target name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('WHOIS', irc.ERR_NOSUCHNICK)
|
||||
|
||||
# Not on channel
|
||||
|
||||
|
||||
def test_PART(self):
|
||||
"""
|
||||
Tests that irc_PART sends ERR_NOTONCHANNEL if the target name can't
|
||||
be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('PART', irc.ERR_NOTONCHANNEL)
|
||||
|
||||
# Probably nothing
|
||||
|
||||
|
||||
def test_WHO(self):
|
||||
"""
|
||||
Tests that irc_WHO immediately ends the WHO list if the target name
|
||||
can't be decoded.
|
||||
"""
|
||||
self.assertChokesOnBadBytes('WHO', irc.RPL_ENDOFWHO)
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.im.ircsupport}.
|
||||
"""
|
||||
|
||||
from twisted.test.proto_helpers import StringTransport
|
||||
|
||||
from twisted.words.im.basechat import ChatUI, Conversation, GroupConversation
|
||||
from twisted.words.im.ircsupport import IRCAccount, IRCProto
|
||||
from twisted.words.im.locals import OfflineError
|
||||
from twisted.words.test.test_irc import IRCTestCase
|
||||
|
||||
|
||||
|
||||
class StubConversation(Conversation):
|
||||
def show(self):
|
||||
pass
|
||||
|
||||
|
||||
def showMessage(self, message, metadata):
|
||||
self.message = message
|
||||
self.metadata = metadata
|
||||
|
||||
|
||||
|
||||
class StubGroupConversation(GroupConversation):
|
||||
def setTopic(self, topic, nickname):
|
||||
self.topic = topic
|
||||
self.topicSetBy = nickname
|
||||
|
||||
|
||||
def show(self):
|
||||
pass
|
||||
|
||||
|
||||
def showGroupMessage(self, sender, text, metadata=None):
|
||||
self.metadata = metadata
|
||||
self.text = text
|
||||
self.metadata = metadata
|
||||
|
||||
|
||||
|
||||
class StubChatUI(ChatUI):
|
||||
def getConversation(self, group, Class=StubConversation, stayHidden=0):
|
||||
return ChatUI.getGroupConversation(self, group, Class, stayHidden)
|
||||
|
||||
|
||||
def getGroupConversation(self, group, Class=StubGroupConversation, stayHidden=0):
|
||||
return ChatUI.getGroupConversation(self, group, Class, stayHidden)
|
||||
|
||||
|
||||
|
||||
class IRCProtoTests(IRCTestCase):
|
||||
"""
|
||||
Tests for L{IRCProto}.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.account = IRCAccount(
|
||||
"Some account", False, "alice", None, "example.com", 6667)
|
||||
self.proto = IRCProto(self.account, StubChatUI(), None)
|
||||
self.transport = StringTransport()
|
||||
|
||||
|
||||
def test_login(self):
|
||||
"""
|
||||
When L{IRCProto} is connected to a transport, it sends I{NICK} and
|
||||
I{USER} commands with the username from the account object.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.assertEqualBufferValue(
|
||||
self.transport.value(),
|
||||
"NICK alice\r\n"
|
||||
"USER alice foo bar :Twisted-IM user\r\n")
|
||||
|
||||
|
||||
def test_authenticate(self):
|
||||
"""
|
||||
If created with an account with a password, L{IRCProto} sends a
|
||||
I{PASS} command before the I{NICK} and I{USER} commands.
|
||||
"""
|
||||
self.account.password = "secret"
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.assertEqualBufferValue(
|
||||
self.transport.value(),
|
||||
"PASS secret\r\n"
|
||||
"NICK alice\r\n"
|
||||
"USER alice foo bar :Twisted-IM user\r\n")
|
||||
|
||||
|
||||
def test_channels(self):
|
||||
"""
|
||||
If created with an account with a list of channels, L{IRCProto}
|
||||
joins each of those channels after registering.
|
||||
"""
|
||||
self.account.channels = ['#foo', '#bar']
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.assertEqualBufferValue(
|
||||
self.transport.value(),
|
||||
"NICK alice\r\n"
|
||||
"USER alice foo bar :Twisted-IM user\r\n"
|
||||
"JOIN #foo\r\n"
|
||||
"JOIN #bar\r\n")
|
||||
|
||||
|
||||
def test_isupport(self):
|
||||
"""
|
||||
L{IRCProto} can interpret I{ISUPPORT} (I{005}) messages from the server
|
||||
and reflect their information in its C{supported} attribute.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(
|
||||
":irc.example.com 005 alice MODES=4 CHANLIMIT=#:20\r\n")
|
||||
self.assertEqual(4, self.proto.supported.getFeature("MODES"))
|
||||
|
||||
|
||||
def test_nick(self):
|
||||
"""
|
||||
IRC NICK command changes the nickname of a user.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice JOIN #group1\r\n")
|
||||
self.proto.dataReceived(":alice1 JOIN #group1\r\n")
|
||||
self.proto.dataReceived(":alice1 NICK newnick\r\n")
|
||||
self.proto.dataReceived(":alice3 NICK newnick3\r\n")
|
||||
self.assertIn("newnick", self.proto._ingroups)
|
||||
self.assertNotIn("alice1", self.proto._ingroups)
|
||||
|
||||
|
||||
def test_part(self):
|
||||
"""
|
||||
IRC PART command removes a user from an IRC channel.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice1 JOIN #group1\r\n")
|
||||
self.assertIn("group1", self.proto._ingroups["alice1"])
|
||||
self.assertNotIn("group2", self.proto._ingroups["alice1"])
|
||||
self.proto.dataReceived(":alice PART #group1\r\n")
|
||||
self.proto.dataReceived(":alice1 PART #group1\r\n")
|
||||
self.proto.dataReceived(":alice1 PART #group2\r\n")
|
||||
self.assertNotIn("group1", self.proto._ingroups["alice1"])
|
||||
self.assertNotIn("group2", self.proto._ingroups["alice1"])
|
||||
|
||||
|
||||
def test_quit(self):
|
||||
"""
|
||||
IRC QUIT command removes a user from all IRC channels.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice1 JOIN #group1\r\n")
|
||||
self.assertIn("group1", self.proto._ingroups["alice1"])
|
||||
self.assertNotIn("group2", self.proto._ingroups["alice1"])
|
||||
self.proto.dataReceived(":alice1 JOIN #group3\r\n")
|
||||
self.assertIn("group3", self.proto._ingroups["alice1"])
|
||||
self.proto.dataReceived(":alice1 QUIT\r\n")
|
||||
self.assertTrue(len(self.proto._ingroups["alice1"]) == 0)
|
||||
self.proto.dataReceived(":alice3 QUIT\r\n")
|
||||
|
||||
|
||||
def test_topic(self):
|
||||
"""
|
||||
IRC TOPIC command changes the topic of an IRC channel.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice1 JOIN #group1\r\n")
|
||||
self.proto.dataReceived(":alice1 TOPIC #group1 newtopic\r\n")
|
||||
groupConversation = self.proto.getGroupConversation("group1")
|
||||
self.assertEqual(groupConversation.topic, "newtopic")
|
||||
self.assertEqual(groupConversation.topicSetBy, "alice1")
|
||||
|
||||
|
||||
def test_privmsg(self):
|
||||
"""
|
||||
PRIVMSG sends a private message to a user or channel.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice1 PRIVMSG t2 test_message_1\r\n")
|
||||
conversation = self.proto.chat.getConversation(
|
||||
self.proto.getPerson("alice1"))
|
||||
self.assertEqual(conversation.message, "test_message_1")
|
||||
|
||||
self.proto.dataReceived(":alice1 PRIVMSG #group1 test_message_2\r\n")
|
||||
groupConversation = self.proto.getGroupConversation("group1")
|
||||
self.assertEqual(groupConversation.text, "test_message_2")
|
||||
|
||||
self.proto.setNick("alice")
|
||||
self.proto.dataReceived(":alice PRIVMSG #foo test_message_3\r\n")
|
||||
groupConversation = self.proto.getGroupConversation("foo")
|
||||
self.assertFalse(hasattr(groupConversation, "text"))
|
||||
conversation = self.proto.chat.getConversation(
|
||||
self.proto.getPerson("alice"))
|
||||
self.assertFalse(hasattr(conversation, "message"))
|
||||
|
||||
|
||||
def test_action(self):
|
||||
"""
|
||||
CTCP ACTION to a user or channel.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(":alice1 PRIVMSG alice1 :\01ACTION smiles\01\r\n")
|
||||
conversation = self.proto.chat.getConversation(
|
||||
self.proto.getPerson("alice1"))
|
||||
self.assertEqual(conversation.message, "smiles")
|
||||
|
||||
self.proto.dataReceived(":alice1 PRIVMSG #group1 :\01ACTION laughs\01\r\n")
|
||||
groupConversation = self.proto.getGroupConversation("group1")
|
||||
self.assertEqual(groupConversation.text, "laughs")
|
||||
|
||||
self.proto.setNick("alice")
|
||||
self.proto.dataReceived(":alice PRIVMSG #group1 :\01ACTION cries\01\r\n")
|
||||
groupConversation = self.proto.getGroupConversation("foo")
|
||||
self.assertFalse(hasattr(groupConversation, "text"))
|
||||
conversation = self.proto.chat.getConversation(
|
||||
self.proto.getPerson("alice"))
|
||||
self.assertFalse(hasattr(conversation, "message"))
|
||||
|
||||
|
||||
def test_rplNamreply(self):
|
||||
"""
|
||||
RPL_NAMREPLY server response (353) lists all the users in a channel.
|
||||
RPL_ENDOFNAMES server response (363) is sent at the end of RPL_NAMREPLY
|
||||
to indicate that there are no more names.
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(
|
||||
":example.com 353 z3p = #bnl :pSwede Dan- SkOyg @MrOp +MrPlus\r\n")
|
||||
expectedInGroups = {'Dan-': ['bnl'],
|
||||
'pSwede': ['bnl'],
|
||||
'SkOyg': ['bnl'],
|
||||
'MrOp': ['bnl'],
|
||||
'MrPlus': ['bnl']}
|
||||
expectedNamReplies = {
|
||||
'bnl': ['pSwede', 'Dan-', 'SkOyg', 'MrOp', 'MrPlus']}
|
||||
self.assertEqual(expectedInGroups, self.proto._ingroups)
|
||||
self.assertEqual(expectedNamReplies, self.proto._namreplies)
|
||||
|
||||
self.proto.dataReceived(
|
||||
":example.com 366 alice #bnl :End of /NAMES list\r\n")
|
||||
self.assertEqual({}, self.proto._namreplies)
|
||||
groupConversation = self.proto.getGroupConversation("bnl")
|
||||
self.assertEqual(expectedNamReplies['bnl'], groupConversation.members)
|
||||
|
||||
|
||||
def test_rplTopic(self):
|
||||
"""
|
||||
RPL_TOPIC server response (332) is sent when a channel's topic is changed
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
self.proto.dataReceived(
|
||||
":example.com 332 alice, #foo :Some random topic\r\n")
|
||||
self.assertEqual("Some random topic", self.proto._topics["foo"])
|
||||
|
||||
|
||||
def test_sendMessage(self):
|
||||
"""
|
||||
L{IRCPerson.sendMessage}
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
person = self.proto.getPerson("alice")
|
||||
self.assertRaises(OfflineError, person.sendMessage, "Some message")
|
||||
|
||||
person.account.client = self.proto
|
||||
self.transport.clear()
|
||||
person.sendMessage("Some message 2")
|
||||
self.assertEqual(self.transport.io.getvalue(),
|
||||
b'PRIVMSG alice :Some message 2\r\n')
|
||||
|
||||
self.transport.clear()
|
||||
person.sendMessage("smiles", {"style": "emote"})
|
||||
self.assertEqual(self.transport.io.getvalue(),
|
||||
b'PRIVMSG alice :\x01ACTION smiles\x01\r\n')
|
||||
|
||||
|
||||
def test_sendGroupMessage(self):
|
||||
"""
|
||||
L{IRCGroup.sendGroupMessage}
|
||||
"""
|
||||
self.proto.makeConnection(self.transport)
|
||||
group = self.proto.chat.getGroup("#foo", self.proto)
|
||||
self.assertRaises(OfflineError, group.sendGroupMessage, "Some message")
|
||||
|
||||
group.account.client = self.proto
|
||||
self.transport.clear()
|
||||
group.sendGroupMessage("Some message 2")
|
||||
self.assertEqual(self.transport.io.getvalue(),
|
||||
b'PRIVMSG #foo :Some message 2\r\n')
|
||||
|
||||
self.transport.clear()
|
||||
group.sendGroupMessage("smiles", {"style": "emote"})
|
||||
self.assertEqual(self.transport.io.getvalue(),
|
||||
b'PRIVMSG #foo :\x01ACTION smiles\x01\r\n')
|
||||
|
|
@ -0,0 +1,497 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.client}
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from hashlib import sha1
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.protocols.jabber import client, error, jid, xmlstream
|
||||
from twisted.words.protocols.jabber.sasl import SASLInitiatingInitializer
|
||||
from twisted.words.xish import utility
|
||||
|
||||
try:
|
||||
from twisted.internet import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
skipWhenNoSSL = "SSL not available"
|
||||
else:
|
||||
skipWhenNoSSL = None
|
||||
|
||||
IQ_AUTH_GET = '/iq[@type="get"]/query[@xmlns="jabber:iq:auth"]'
|
||||
IQ_AUTH_SET = '/iq[@type="set"]/query[@xmlns="jabber:iq:auth"]'
|
||||
NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
|
||||
IQ_BIND_SET = '/iq[@type="set"]/bind[@xmlns="%s"]' % NS_BIND
|
||||
NS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session'
|
||||
IQ_SESSION_SET = '/iq[@type="set"]/session[@xmlns="%s"]' % NS_SESSION
|
||||
|
||||
class CheckVersionInitializerTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
a = xmlstream.Authenticator()
|
||||
xs = xmlstream.XmlStream(a)
|
||||
self.init = client.CheckVersionInitializer(xs)
|
||||
|
||||
|
||||
def testSupported(self):
|
||||
"""
|
||||
Test supported version number 1.0
|
||||
"""
|
||||
self.init.xmlstream.version = (1, 0)
|
||||
self.init.initialize()
|
||||
|
||||
|
||||
def testNotSupported(self):
|
||||
"""
|
||||
Test unsupported version number 0.0, and check exception.
|
||||
"""
|
||||
self.init.xmlstream.version = (0, 0)
|
||||
exc = self.assertRaises(error.StreamError, self.init.initialize)
|
||||
self.assertEqual('unsupported-version', exc.condition)
|
||||
|
||||
|
||||
|
||||
class InitiatingInitializerHarness(object):
|
||||
"""
|
||||
Testing harness for interacting with XML stream initializers.
|
||||
|
||||
This sets up an L{utility.XmlPipe} to create a communication channel between
|
||||
the initializer and the stubbed receiving entity. It features a sink and
|
||||
source side that both act similarly to a real L{xmlstream.XmlStream}. The
|
||||
sink is augmented with an authenticator to which initializers can be added.
|
||||
|
||||
The harness also provides some utility methods to work with event observers
|
||||
and deferreds.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.output = []
|
||||
self.pipe = utility.XmlPipe()
|
||||
self.xmlstream = self.pipe.sink
|
||||
self.authenticator = xmlstream.ConnectAuthenticator('example.org')
|
||||
self.xmlstream.authenticator = self.authenticator
|
||||
|
||||
|
||||
def waitFor(self, event, handler):
|
||||
"""
|
||||
Observe an output event, returning a deferred.
|
||||
|
||||
The returned deferred will be fired when the given event has been
|
||||
observed on the source end of the L{XmlPipe} tied to the protocol
|
||||
under test. The handler is added as the first callback.
|
||||
|
||||
@param event: The event to be observed. See
|
||||
L{utility.EventDispatcher.addOnetimeObserver}.
|
||||
@param handler: The handler to be called with the observed event object.
|
||||
@rtype: L{defer.Deferred}.
|
||||
"""
|
||||
d = defer.Deferred()
|
||||
d.addCallback(handler)
|
||||
self.pipe.source.addOnetimeObserver(event, d.callback)
|
||||
return d
|
||||
|
||||
|
||||
|
||||
class IQAuthInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
|
||||
"""
|
||||
Tests for L{client.IQAuthInitializer}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(IQAuthInitializerTests, self).setUp()
|
||||
self.init = client.IQAuthInitializer(self.xmlstream)
|
||||
self.authenticator.jid = jid.JID('user@example.com/resource')
|
||||
self.authenticator.password = u'secret'
|
||||
|
||||
|
||||
def testPlainText(self):
|
||||
"""
|
||||
Test plain-text authentication.
|
||||
|
||||
Act as a server supporting plain-text authentication and expect the
|
||||
C{password} field to be filled with the password. Then act as if
|
||||
authentication succeeds.
|
||||
"""
|
||||
|
||||
def onAuthGet(iq):
|
||||
"""
|
||||
Called when the initializer sent a query for authentication methods.
|
||||
|
||||
The response informs the client that plain-text authentication
|
||||
is supported.
|
||||
"""
|
||||
|
||||
# Create server response
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
response.addElement(('jabber:iq:auth', 'query'))
|
||||
response.query.addElement('username')
|
||||
response.query.addElement('password')
|
||||
response.query.addElement('resource')
|
||||
|
||||
# Set up an observer for the next request we expect.
|
||||
d = self.waitFor(IQ_AUTH_SET, onAuthSet)
|
||||
|
||||
# Send server response
|
||||
self.pipe.source.send(response)
|
||||
|
||||
return d
|
||||
|
||||
def onAuthSet(iq):
|
||||
"""
|
||||
Called when the initializer sent the authentication request.
|
||||
|
||||
The server checks the credentials and responds with an empty result
|
||||
signalling success.
|
||||
"""
|
||||
self.assertEqual('user', unicode(iq.query.username))
|
||||
self.assertEqual('secret', unicode(iq.query.password))
|
||||
self.assertEqual('resource', unicode(iq.query.resource))
|
||||
|
||||
# Send server response
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
self.pipe.source.send(response)
|
||||
|
||||
# Set up an observer for the request for authentication fields
|
||||
d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
|
||||
|
||||
# Start the initializer
|
||||
d2 = self.init.initialize()
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
def testDigest(self):
|
||||
"""
|
||||
Test digest authentication.
|
||||
|
||||
Act as a server supporting digest authentication and expect the
|
||||
C{digest} field to be filled with a sha1 digest of the concatenated
|
||||
stream session identifier and password. Then act as if authentication
|
||||
succeeds.
|
||||
"""
|
||||
|
||||
def onAuthGet(iq):
|
||||
"""
|
||||
Called when the initializer sent a query for authentication methods.
|
||||
|
||||
The response informs the client that digest authentication is
|
||||
supported.
|
||||
"""
|
||||
|
||||
# Create server response
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
response.addElement(('jabber:iq:auth', 'query'))
|
||||
response.query.addElement('username')
|
||||
response.query.addElement('digest')
|
||||
response.query.addElement('resource')
|
||||
|
||||
# Set up an observer for the next request we expect.
|
||||
d = self.waitFor(IQ_AUTH_SET, onAuthSet)
|
||||
|
||||
# Send server response
|
||||
self.pipe.source.send(response)
|
||||
|
||||
return d
|
||||
|
||||
def onAuthSet(iq):
|
||||
"""
|
||||
Called when the initializer sent the authentication request.
|
||||
|
||||
The server checks the credentials and responds with an empty result
|
||||
signalling success.
|
||||
"""
|
||||
self.assertEqual('user', unicode(iq.query.username))
|
||||
self.assertEqual(sha1(b'12345secret').hexdigest(),
|
||||
unicode(iq.query.digest))
|
||||
self.assertEqual('resource', unicode(iq.query.resource))
|
||||
|
||||
# Send server response
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
self.pipe.source.send(response)
|
||||
|
||||
# Digest authentication relies on the stream session identifier. Set it.
|
||||
self.xmlstream.sid = u'12345'
|
||||
|
||||
# Set up an observer for the request for authentication fields
|
||||
d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
|
||||
|
||||
# Start the initializer
|
||||
d2 = self.init.initialize()
|
||||
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
def testFailRequestFields(self):
|
||||
"""
|
||||
Test initializer failure of request for fields for authentication.
|
||||
"""
|
||||
def onAuthGet(iq):
|
||||
"""
|
||||
Called when the initializer sent a query for authentication methods.
|
||||
|
||||
The server responds that the client is not authorized to authenticate.
|
||||
"""
|
||||
response = error.StanzaError('not-authorized').toResponse(iq)
|
||||
self.pipe.source.send(response)
|
||||
|
||||
# Set up an observer for the request for authentication fields
|
||||
d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
|
||||
|
||||
# Start the initializer
|
||||
d2 = self.init.initialize()
|
||||
|
||||
# The initialized should fail with a stanza error.
|
||||
self.assertFailure(d2, error.StanzaError)
|
||||
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
def testFailAuth(self):
|
||||
"""
|
||||
Test initializer failure to authenticate.
|
||||
"""
|
||||
|
||||
def onAuthGet(iq):
|
||||
"""
|
||||
Called when the initializer sent a query for authentication methods.
|
||||
|
||||
The response informs the client that plain-text authentication
|
||||
is supported.
|
||||
"""
|
||||
|
||||
# Send server response
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
response.addElement(('jabber:iq:auth', 'query'))
|
||||
response.query.addElement('username')
|
||||
response.query.addElement('password')
|
||||
response.query.addElement('resource')
|
||||
|
||||
# Set up an observer for the next request we expect.
|
||||
d = self.waitFor(IQ_AUTH_SET, onAuthSet)
|
||||
|
||||
# Send server response
|
||||
self.pipe.source.send(response)
|
||||
|
||||
return d
|
||||
|
||||
def onAuthSet(iq):
|
||||
"""
|
||||
Called when the initializer sent the authentication request.
|
||||
|
||||
The server checks the credentials and responds with a not-authorized
|
||||
stanza error.
|
||||
"""
|
||||
response = error.StanzaError('not-authorized').toResponse(iq)
|
||||
self.pipe.source.send(response)
|
||||
|
||||
# Set up an observer for the request for authentication fields
|
||||
d1 = self.waitFor(IQ_AUTH_GET, onAuthGet)
|
||||
|
||||
# Start the initializer
|
||||
d2 = self.init.initialize()
|
||||
|
||||
# The initializer should fail with a stanza error.
|
||||
self.assertFailure(d2, error.StanzaError)
|
||||
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
|
||||
class BindInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
|
||||
"""
|
||||
Tests for L{client.BindInitializer}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(BindInitializerTests, self).setUp()
|
||||
self.init = client.BindInitializer(self.xmlstream)
|
||||
self.authenticator.jid = jid.JID('user@example.com/resource')
|
||||
|
||||
|
||||
def testBasic(self):
|
||||
"""
|
||||
Set up a stream, and act as if resource binding succeeds.
|
||||
"""
|
||||
def onBind(iq):
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
response.addElement((NS_BIND, 'bind'))
|
||||
response.bind.addElement('jid',
|
||||
content=u'user@example.com/other resource')
|
||||
self.pipe.source.send(response)
|
||||
|
||||
def cb(result):
|
||||
self.assertEqual(jid.JID('user@example.com/other resource'),
|
||||
self.authenticator.jid)
|
||||
|
||||
d1 = self.waitFor(IQ_BIND_SET, onBind)
|
||||
d2 = self.init.start()
|
||||
d2.addCallback(cb)
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
def testFailure(self):
|
||||
"""
|
||||
Set up a stream, and act as if resource binding fails.
|
||||
"""
|
||||
def onBind(iq):
|
||||
response = error.StanzaError('conflict').toResponse(iq)
|
||||
self.pipe.source.send(response)
|
||||
|
||||
d1 = self.waitFor(IQ_BIND_SET, onBind)
|
||||
d2 = self.init.start()
|
||||
self.assertFailure(d2, error.StanzaError)
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
|
||||
class SessionInitializerTests(InitiatingInitializerHarness, unittest.TestCase):
|
||||
"""
|
||||
Tests for L{client.SessionInitializer}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(SessionInitializerTests, self).setUp()
|
||||
self.init = client.SessionInitializer(self.xmlstream)
|
||||
|
||||
|
||||
def testSuccess(self):
|
||||
"""
|
||||
Set up a stream, and act as if session establishment succeeds.
|
||||
"""
|
||||
|
||||
def onSession(iq):
|
||||
response = xmlstream.toResponse(iq, 'result')
|
||||
self.pipe.source.send(response)
|
||||
|
||||
d1 = self.waitFor(IQ_SESSION_SET, onSession)
|
||||
d2 = self.init.start()
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
def testFailure(self):
|
||||
"""
|
||||
Set up a stream, and act as if session establishment fails.
|
||||
"""
|
||||
def onSession(iq):
|
||||
response = error.StanzaError('forbidden').toResponse(iq)
|
||||
self.pipe.source.send(response)
|
||||
|
||||
d1 = self.waitFor(IQ_SESSION_SET, onSession)
|
||||
d2 = self.init.start()
|
||||
self.assertFailure(d2, error.StanzaError)
|
||||
return defer.gatherResults([d1, d2])
|
||||
|
||||
|
||||
|
||||
class BasicAuthenticatorTests(unittest.TestCase):
|
||||
"""
|
||||
Test for both BasicAuthenticator and basicClientFactory.
|
||||
"""
|
||||
|
||||
def test_basic(self):
|
||||
"""
|
||||
Authenticator and stream are properly constructed by the factory.
|
||||
|
||||
The L{xmlstream.XmlStream} protocol created by the factory has the new
|
||||
L{client.BasicAuthenticator} instance in its C{authenticator}
|
||||
attribute. It is set up with C{jid} and C{password} as passed to the
|
||||
factory, C{otherHost} taken from the client JID. The stream futher has
|
||||
two initializers, for TLS and authentication, of which the first has
|
||||
its C{required} attribute set to C{True}.
|
||||
"""
|
||||
self.client_jid = jid.JID('user@example.com/resource')
|
||||
|
||||
# Get an XmlStream instance. Note that it gets initialized with the
|
||||
# XMPPAuthenticator (that has its associateWithXmlStream called) that
|
||||
# is in turn initialized with the arguments to the factory.
|
||||
xs = client.basicClientFactory(self.client_jid,
|
||||
'secret').buildProtocol(None)
|
||||
|
||||
# test authenticator's instance variables
|
||||
self.assertEqual('example.com', xs.authenticator.otherHost)
|
||||
self.assertEqual(self.client_jid, xs.authenticator.jid)
|
||||
self.assertEqual('secret', xs.authenticator.password)
|
||||
|
||||
# test list of initializers
|
||||
tls, auth = xs.initializers
|
||||
|
||||
self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
|
||||
self.assertIsInstance(auth, client.IQAuthInitializer)
|
||||
|
||||
self.assertFalse(tls.required)
|
||||
|
||||
|
||||
|
||||
class XMPPAuthenticatorTests(unittest.TestCase):
|
||||
"""
|
||||
Test for both XMPPAuthenticator and XMPPClientFactory.
|
||||
"""
|
||||
|
||||
def test_basic(self):
|
||||
"""
|
||||
Test basic operations.
|
||||
|
||||
Setup an XMPPClientFactory, which sets up an XMPPAuthenticator, and let
|
||||
it produce a protocol instance. Then inspect the instance variables of
|
||||
the authenticator and XML stream objects.
|
||||
"""
|
||||
self.client_jid = jid.JID('user@example.com/resource')
|
||||
|
||||
# Get an XmlStream instance. Note that it gets initialized with the
|
||||
# XMPPAuthenticator (that has its associateWithXmlStream called) that
|
||||
# is in turn initialized with the arguments to the factory.
|
||||
xs = client.XMPPClientFactory(self.client_jid,
|
||||
'secret').buildProtocol(None)
|
||||
|
||||
# test authenticator's instance variables
|
||||
self.assertEqual('example.com', xs.authenticator.otherHost)
|
||||
self.assertEqual(self.client_jid, xs.authenticator.jid)
|
||||
self.assertEqual('secret', xs.authenticator.password)
|
||||
|
||||
# test list of initializers
|
||||
version, tls, sasl, bind, session = xs.initializers
|
||||
|
||||
self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
|
||||
self.assertIsInstance(sasl, SASLInitiatingInitializer)
|
||||
self.assertIsInstance(bind, client.BindInitializer)
|
||||
self.assertIsInstance(session, client.SessionInitializer)
|
||||
|
||||
self.assertTrue(tls.required)
|
||||
self.assertTrue(sasl.required)
|
||||
self.assertTrue(bind.required)
|
||||
self.assertFalse(session.required)
|
||||
|
||||
|
||||
def test_tlsConfiguration(self):
|
||||
"""
|
||||
A TLS configuration is passed to the TLS initializer.
|
||||
"""
|
||||
configs = []
|
||||
|
||||
def init(self, xs, required=True, configurationForTLS=None):
|
||||
configs.append(configurationForTLS)
|
||||
|
||||
self.client_jid = jid.JID('user@example.com/resource')
|
||||
|
||||
# Get an XmlStream instance. Note that it gets initialized with the
|
||||
# XMPPAuthenticator (that has its associateWithXmlStream called) that
|
||||
# is in turn initialized with the arguments to the factory.
|
||||
configurationForTLS = ssl.CertificateOptions()
|
||||
factory = client.XMPPClientFactory(
|
||||
self.client_jid, 'secret',
|
||||
configurationForTLS=configurationForTLS)
|
||||
self.patch(xmlstream.TLSInitiatingInitializer, "__init__", init)
|
||||
xs = factory.buildProtocol(None)
|
||||
|
||||
# test list of initializers
|
||||
version, tls, sasl, bind, session = xs.initializers
|
||||
|
||||
self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
|
||||
self.assertIs(configurationForTLS, configs[0])
|
||||
|
||||
|
||||
test_tlsConfiguration.skip = skipWhenNoSSL
|
||||
|
|
@ -0,0 +1,440 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.component}
|
||||
"""
|
||||
from hashlib import sha1
|
||||
|
||||
from zope.interface.verify import verifyObject
|
||||
|
||||
from twisted.python import failure
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.protocols.jabber import component, ijabber, xmlstream
|
||||
from twisted.words.protocols.jabber.jid import JID
|
||||
from twisted.words.xish import domish
|
||||
from twisted.words.xish.utility import XmlPipe
|
||||
|
||||
class DummyTransport:
|
||||
def __init__(self, list):
|
||||
self.list = list
|
||||
|
||||
def write(self, bytes):
|
||||
self.list.append(bytes)
|
||||
|
||||
class ComponentInitiatingInitializerTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.output = []
|
||||
|
||||
self.authenticator = xmlstream.Authenticator()
|
||||
self.authenticator.password = u'secret'
|
||||
self.xmlstream = xmlstream.XmlStream(self.authenticator)
|
||||
self.xmlstream.namespace = 'test:component'
|
||||
self.xmlstream.send = self.output.append
|
||||
self.xmlstream.connectionMade()
|
||||
self.xmlstream.dataReceived(
|
||||
"<stream:stream xmlns='test:component' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"from='example.com' id='12345' version='1.0'>")
|
||||
self.xmlstream.sid = u'12345'
|
||||
self.init = component.ComponentInitiatingInitializer(self.xmlstream)
|
||||
|
||||
def testHandshake(self):
|
||||
"""
|
||||
Test basic operations of component handshake.
|
||||
"""
|
||||
|
||||
d = self.init.initialize()
|
||||
|
||||
# the initializer should have sent the handshake request
|
||||
|
||||
handshake = self.output[-1]
|
||||
self.assertEqual('handshake', handshake.name)
|
||||
self.assertEqual('test:component', handshake.uri)
|
||||
self.assertEqual(sha1(b'12345' + b'secret').hexdigest(),
|
||||
unicode(handshake))
|
||||
|
||||
# successful authentication
|
||||
|
||||
handshake.children = []
|
||||
self.xmlstream.dataReceived(handshake.toXml())
|
||||
|
||||
return d
|
||||
|
||||
class ComponentAuthTests(unittest.TestCase):
|
||||
def authPassed(self, stream):
|
||||
self.authComplete = True
|
||||
|
||||
def testAuth(self):
|
||||
self.authComplete = False
|
||||
outlist = []
|
||||
|
||||
ca = component.ConnectComponentAuthenticator(u"cjid", u"secret")
|
||||
xs = xmlstream.XmlStream(ca)
|
||||
xs.transport = DummyTransport(outlist)
|
||||
|
||||
xs.addObserver(xmlstream.STREAM_AUTHD_EVENT,
|
||||
self.authPassed)
|
||||
|
||||
# Go...
|
||||
xs.connectionMade()
|
||||
xs.dataReceived(b"<stream:stream xmlns='jabber:component:accept' xmlns:stream='http://etherx.jabber.org/streams' from='cjid' id='12345'>")
|
||||
|
||||
# Calculate what we expect the handshake value to be
|
||||
hv = sha1(b"12345" + b"secret").hexdigest().encode('ascii')
|
||||
|
||||
self.assertEqual(outlist[1], b"<handshake>" + hv + b"</handshake>")
|
||||
|
||||
xs.dataReceived("<handshake/>")
|
||||
|
||||
self.assertEqual(self.authComplete, True)
|
||||
|
||||
|
||||
|
||||
class ServiceTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{component.Service}.
|
||||
"""
|
||||
|
||||
def test_interface(self):
|
||||
"""
|
||||
L{component.Service} implements L{ijabber.IService}.
|
||||
"""
|
||||
service = component.Service()
|
||||
verifyObject(ijabber.IService, service)
|
||||
|
||||
|
||||
|
||||
class JabberServiceHarness(component.Service):
|
||||
def __init__(self):
|
||||
self.componentConnectedFlag = False
|
||||
self.componentDisconnectedFlag = False
|
||||
self.transportConnectedFlag = False
|
||||
|
||||
def componentConnected(self, xmlstream):
|
||||
self.componentConnectedFlag = True
|
||||
|
||||
def componentDisconnected(self):
|
||||
self.componentDisconnectedFlag = True
|
||||
|
||||
def transportConnected(self, xmlstream):
|
||||
self.transportConnectedFlag = True
|
||||
|
||||
|
||||
class JabberServiceManagerTests(unittest.TestCase):
|
||||
def testSM(self):
|
||||
# Setup service manager and test harnes
|
||||
sm = component.ServiceManager("foo", "password")
|
||||
svc = JabberServiceHarness()
|
||||
svc.setServiceParent(sm)
|
||||
|
||||
# Create a write list
|
||||
wlist = []
|
||||
|
||||
# Setup a XmlStream
|
||||
xs = sm.getFactory().buildProtocol(None)
|
||||
xs.transport = self
|
||||
xs.transport.write = wlist.append
|
||||
|
||||
# Indicate that it's connected
|
||||
xs.connectionMade()
|
||||
|
||||
# Ensure the test service harness got notified
|
||||
self.assertEqual(True, svc.transportConnectedFlag)
|
||||
|
||||
# Jump ahead and pretend like the stream got auth'd
|
||||
xs.dispatch(xs, xmlstream.STREAM_AUTHD_EVENT)
|
||||
|
||||
# Ensure the test service harness got notified
|
||||
self.assertEqual(True, svc.componentConnectedFlag)
|
||||
|
||||
# Pretend to drop the connection
|
||||
xs.connectionLost(None)
|
||||
|
||||
# Ensure the test service harness got notified
|
||||
self.assertEqual(True, svc.componentDisconnectedFlag)
|
||||
|
||||
|
||||
|
||||
class RouterTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{component.Router}.
|
||||
"""
|
||||
|
||||
def test_addRoute(self):
|
||||
"""
|
||||
Test route registration and routing on incoming stanzas.
|
||||
"""
|
||||
router = component.Router()
|
||||
routed = []
|
||||
router.route = lambda element: routed.append(element)
|
||||
|
||||
pipe = XmlPipe()
|
||||
router.addRoute('example.org', pipe.sink)
|
||||
self.assertEqual(1, len(router.routes))
|
||||
self.assertEqual(pipe.sink, router.routes['example.org'])
|
||||
|
||||
element = domish.Element(('testns', 'test'))
|
||||
pipe.source.send(element)
|
||||
self.assertEqual([element], routed)
|
||||
|
||||
|
||||
def test_route(self):
|
||||
"""
|
||||
Test routing of a message.
|
||||
"""
|
||||
component1 = XmlPipe()
|
||||
component2 = XmlPipe()
|
||||
router = component.Router()
|
||||
router.addRoute('component1.example.org', component1.sink)
|
||||
router.addRoute('component2.example.org', component2.sink)
|
||||
|
||||
outgoing = []
|
||||
component2.source.addObserver('/*',
|
||||
lambda element: outgoing.append(element))
|
||||
stanza = domish.Element((None, 'presence'))
|
||||
stanza['from'] = 'component1.example.org'
|
||||
stanza['to'] = 'component2.example.org'
|
||||
component1.source.send(stanza)
|
||||
self.assertEqual([stanza], outgoing)
|
||||
|
||||
|
||||
def test_routeDefault(self):
|
||||
"""
|
||||
Test routing of a message using the default route.
|
||||
|
||||
The default route is the one with L{None} as its key in the
|
||||
routing table. It is taken when there is no more specific route
|
||||
in the routing table that matches the stanza's destination.
|
||||
"""
|
||||
component1 = XmlPipe()
|
||||
s2s = XmlPipe()
|
||||
router = component.Router()
|
||||
router.addRoute('component1.example.org', component1.sink)
|
||||
router.addRoute(None, s2s.sink)
|
||||
|
||||
outgoing = []
|
||||
s2s.source.addObserver('/*', lambda element: outgoing.append(element))
|
||||
stanza = domish.Element((None, 'presence'))
|
||||
stanza['from'] = 'component1.example.org'
|
||||
stanza['to'] = 'example.com'
|
||||
component1.source.send(stanza)
|
||||
self.assertEqual([stanza], outgoing)
|
||||
|
||||
|
||||
|
||||
class ListenComponentAuthenticatorTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{component.ListenComponentAuthenticator}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.output = []
|
||||
authenticator = component.ListenComponentAuthenticator('secret')
|
||||
self.xmlstream = xmlstream.XmlStream(authenticator)
|
||||
self.xmlstream.send = self.output.append
|
||||
|
||||
|
||||
def loseConnection(self):
|
||||
"""
|
||||
Stub loseConnection because we are a transport.
|
||||
"""
|
||||
self.xmlstream.connectionLost("no reason")
|
||||
|
||||
|
||||
def test_streamStarted(self):
|
||||
"""
|
||||
The received stream header should set several attributes.
|
||||
"""
|
||||
observers = []
|
||||
|
||||
def addOnetimeObserver(event, observerfn):
|
||||
observers.append((event, observerfn))
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.addOnetimeObserver = addOnetimeObserver
|
||||
|
||||
xs.makeConnection(self)
|
||||
self.assertIdentical(None, xs.sid)
|
||||
self.assertFalse(xs._headerSent)
|
||||
|
||||
xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"to='component.example.org'>")
|
||||
self.assertEqual((0, 0), xs.version)
|
||||
self.assertNotIdentical(None, xs.sid)
|
||||
self.assertTrue(xs._headerSent)
|
||||
self.assertEqual(('/*', xs.authenticator.onElement), observers[-1])
|
||||
|
||||
|
||||
def test_streamStartedWrongNamespace(self):
|
||||
"""
|
||||
The received stream header should have a correct namespace.
|
||||
"""
|
||||
streamErrors = []
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.sendStreamError = streamErrors.append
|
||||
xs.makeConnection(self)
|
||||
xs.dataReceived("<stream:stream xmlns='jabber:client' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
"to='component.example.org'>")
|
||||
self.assertEqual(1, len(streamErrors))
|
||||
self.assertEqual('invalid-namespace', streamErrors[-1].condition)
|
||||
|
||||
|
||||
def test_streamStartedNoTo(self):
|
||||
"""
|
||||
The received stream header should have a 'to' attribute.
|
||||
"""
|
||||
streamErrors = []
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.sendStreamError = streamErrors.append
|
||||
xs.makeConnection(self)
|
||||
xs.dataReceived("<stream:stream xmlns='jabber:component:accept' "
|
||||
"xmlns:stream='http://etherx.jabber.org/streams'>")
|
||||
self.assertEqual(1, len(streamErrors))
|
||||
self.assertEqual('improper-addressing', streamErrors[-1].condition)
|
||||
|
||||
|
||||
def test_onElement(self):
|
||||
"""
|
||||
We expect a handshake element with a hash.
|
||||
"""
|
||||
handshakes = []
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.authenticator.onHandshake = handshakes.append
|
||||
|
||||
handshake = domish.Element(('jabber:component:accept', 'handshake'))
|
||||
handshake.addContent(u'1234')
|
||||
xs.authenticator.onElement(handshake)
|
||||
self.assertEqual('1234', handshakes[-1])
|
||||
|
||||
def test_onElementNotHandshake(self):
|
||||
"""
|
||||
Reject elements that are not handshakes
|
||||
"""
|
||||
handshakes = []
|
||||
streamErrors = []
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.authenticator.onHandshake = handshakes.append
|
||||
xs.sendStreamError = streamErrors.append
|
||||
|
||||
element = domish.Element(('jabber:component:accept', 'message'))
|
||||
xs.authenticator.onElement(element)
|
||||
self.assertFalse(handshakes)
|
||||
self.assertEqual('not-authorized', streamErrors[-1].condition)
|
||||
|
||||
|
||||
def test_onHandshake(self):
|
||||
"""
|
||||
Receiving a handshake matching the secret authenticates the stream.
|
||||
"""
|
||||
authd = []
|
||||
|
||||
def authenticated(xs):
|
||||
authd.append(xs)
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
|
||||
xs.sid = u'1234'
|
||||
theHash = '32532c0f7dbf1253c095b18b18e36d38d94c1256'
|
||||
xs.authenticator.onHandshake(theHash)
|
||||
self.assertEqual('<handshake/>', self.output[-1])
|
||||
self.assertEqual(1, len(authd))
|
||||
|
||||
|
||||
def test_onHandshakeWrongHash(self):
|
||||
"""
|
||||
Receiving a bad handshake should yield a stream error.
|
||||
"""
|
||||
streamErrors = []
|
||||
authd = []
|
||||
|
||||
def authenticated(xs):
|
||||
authd.append(xs)
|
||||
|
||||
xs = self.xmlstream
|
||||
xs.addOnetimeObserver(xmlstream.STREAM_AUTHD_EVENT, authenticated)
|
||||
xs.sendStreamError = streamErrors.append
|
||||
|
||||
xs.sid = u'1234'
|
||||
theHash = '1234'
|
||||
xs.authenticator.onHandshake(theHash)
|
||||
self.assertEqual('not-authorized', streamErrors[-1].condition)
|
||||
self.assertEqual(0, len(authd))
|
||||
|
||||
|
||||
|
||||
class XMPPComponentServerFactoryTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{component.XMPPComponentServerFactory}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.router = component.Router()
|
||||
self.factory = component.XMPPComponentServerFactory(self.router,
|
||||
'secret')
|
||||
self.xmlstream = self.factory.buildProtocol(None)
|
||||
self.xmlstream.thisEntity = JID('component.example.org')
|
||||
|
||||
|
||||
def test_makeConnection(self):
|
||||
"""
|
||||
A new connection increases the stream serial count. No logs by default.
|
||||
"""
|
||||
self.xmlstream.dispatch(self.xmlstream,
|
||||
xmlstream.STREAM_CONNECTED_EVENT)
|
||||
self.assertEqual(0, self.xmlstream.serial)
|
||||
self.assertEqual(1, self.factory.serial)
|
||||
self.assertIdentical(None, self.xmlstream.rawDataInFn)
|
||||
self.assertIdentical(None, self.xmlstream.rawDataOutFn)
|
||||
|
||||
|
||||
def test_makeConnectionLogTraffic(self):
|
||||
"""
|
||||
Setting logTraffic should set up raw data loggers.
|
||||
"""
|
||||
self.factory.logTraffic = True
|
||||
self.xmlstream.dispatch(self.xmlstream,
|
||||
xmlstream.STREAM_CONNECTED_EVENT)
|
||||
self.assertNotIdentical(None, self.xmlstream.rawDataInFn)
|
||||
self.assertNotIdentical(None, self.xmlstream.rawDataOutFn)
|
||||
|
||||
|
||||
def test_onError(self):
|
||||
"""
|
||||
An observer for stream errors should trigger onError to log it.
|
||||
"""
|
||||
self.xmlstream.dispatch(self.xmlstream,
|
||||
xmlstream.STREAM_CONNECTED_EVENT)
|
||||
|
||||
class TestError(Exception):
|
||||
pass
|
||||
|
||||
reason = failure.Failure(TestError())
|
||||
self.xmlstream.dispatch(reason, xmlstream.STREAM_ERROR_EVENT)
|
||||
self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
|
||||
|
||||
|
||||
def test_connectionInitialized(self):
|
||||
"""
|
||||
Make sure a new stream is added to the routing table.
|
||||
"""
|
||||
self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
|
||||
self.assertIn('component.example.org', self.router.routes)
|
||||
self.assertIdentical(self.xmlstream,
|
||||
self.router.routes['component.example.org'])
|
||||
|
||||
|
||||
def test_connectionLost(self):
|
||||
"""
|
||||
Make sure a stream is removed from the routing table on disconnect.
|
||||
"""
|
||||
self.xmlstream.dispatch(self.xmlstream, xmlstream.STREAM_AUTHD_EVENT)
|
||||
self.xmlstream.dispatch(None, xmlstream.STREAM_END_EVENT)
|
||||
self.assertNotIn('component.example.org', self.router.routes)
|
||||
|
|
@ -0,0 +1,333 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.error}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.protocols.jabber import error
|
||||
from twisted.words.xish import domish
|
||||
|
||||
NS_XML = 'http://www.w3.org/XML/1998/namespace'
|
||||
NS_STREAMS = 'http://etherx.jabber.org/streams'
|
||||
NS_XMPP_STREAMS = 'urn:ietf:params:xml:ns:xmpp-streams'
|
||||
NS_XMPP_STANZAS = 'urn:ietf:params:xml:ns:xmpp-stanzas'
|
||||
|
||||
class BaseErrorTests(unittest.TestCase):
|
||||
|
||||
def test_getElementPlain(self):
|
||||
"""
|
||||
Test getting an element for a plain error.
|
||||
"""
|
||||
e = error.BaseError('feature-not-implemented')
|
||||
element = e.getElement()
|
||||
self.assertIdentical(element.uri, None)
|
||||
self.assertEqual(len(element.children), 1)
|
||||
|
||||
def test_getElementText(self):
|
||||
"""
|
||||
Test getting an element for an error with a text.
|
||||
"""
|
||||
e = error.BaseError('feature-not-implemented', u'text')
|
||||
element = e.getElement()
|
||||
self.assertEqual(len(element.children), 2)
|
||||
self.assertEqual(unicode(element.text), 'text')
|
||||
self.assertEqual(element.text.getAttribute((NS_XML, 'lang')), None)
|
||||
|
||||
def test_getElementTextLang(self):
|
||||
"""
|
||||
Test getting an element for an error with a text and language.
|
||||
"""
|
||||
e = error.BaseError('feature-not-implemented', u'text', 'en_US')
|
||||
element = e.getElement()
|
||||
self.assertEqual(len(element.children), 2)
|
||||
self.assertEqual(unicode(element.text), 'text')
|
||||
self.assertEqual(element.text[(NS_XML, 'lang')], 'en_US')
|
||||
|
||||
def test_getElementAppCondition(self):
|
||||
"""
|
||||
Test getting an element for an error with an app specific condition.
|
||||
"""
|
||||
ac = domish.Element(('testns', 'myerror'))
|
||||
e = error.BaseError('feature-not-implemented', appCondition=ac)
|
||||
element = e.getElement()
|
||||
self.assertEqual(len(element.children), 2)
|
||||
self.assertEqual(element.myerror, ac)
|
||||
|
||||
class StreamErrorTests(unittest.TestCase):
|
||||
|
||||
def test_getElementPlain(self):
|
||||
"""
|
||||
Test namespace of the element representation of an error.
|
||||
"""
|
||||
e = error.StreamError('feature-not-implemented')
|
||||
element = e.getElement()
|
||||
self.assertEqual(element.uri, NS_STREAMS)
|
||||
|
||||
def test_getElementConditionNamespace(self):
|
||||
"""
|
||||
Test that the error condition element has the correct namespace.
|
||||
"""
|
||||
e = error.StreamError('feature-not-implemented')
|
||||
element = e.getElement()
|
||||
self.assertEqual(NS_XMPP_STREAMS, getattr(element, 'feature-not-implemented').uri)
|
||||
|
||||
def test_getElementTextNamespace(self):
|
||||
"""
|
||||
Test that the error text element has the correct namespace.
|
||||
"""
|
||||
e = error.StreamError('feature-not-implemented', u'text')
|
||||
element = e.getElement()
|
||||
self.assertEqual(NS_XMPP_STREAMS, element.text.uri)
|
||||
|
||||
|
||||
|
||||
class StanzaErrorTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{error.StreamError}.
|
||||
"""
|
||||
|
||||
|
||||
def test_typeRemoteServerTimeout(self):
|
||||
"""
|
||||
Remote Server Timeout should yield type wait, code 504.
|
||||
"""
|
||||
e = error.StanzaError('remote-server-timeout')
|
||||
self.assertEqual('wait', e.type)
|
||||
self.assertEqual('504', e.code)
|
||||
|
||||
|
||||
def test_getElementPlain(self):
|
||||
"""
|
||||
Test getting an element for a plain stanza error.
|
||||
"""
|
||||
e = error.StanzaError('feature-not-implemented')
|
||||
element = e.getElement()
|
||||
self.assertEqual(element.uri, None)
|
||||
self.assertEqual(element['type'], 'cancel')
|
||||
self.assertEqual(element['code'], '501')
|
||||
|
||||
|
||||
def test_getElementType(self):
|
||||
"""
|
||||
Test getting an element for a stanza error with a given type.
|
||||
"""
|
||||
e = error.StanzaError('feature-not-implemented', 'auth')
|
||||
element = e.getElement()
|
||||
self.assertEqual(element.uri, None)
|
||||
self.assertEqual(element['type'], 'auth')
|
||||
self.assertEqual(element['code'], '501')
|
||||
|
||||
|
||||
def test_getElementConditionNamespace(self):
|
||||
"""
|
||||
Test that the error condition element has the correct namespace.
|
||||
"""
|
||||
e = error.StanzaError('feature-not-implemented')
|
||||
element = e.getElement()
|
||||
self.assertEqual(NS_XMPP_STANZAS, getattr(element, 'feature-not-implemented').uri)
|
||||
|
||||
|
||||
def test_getElementTextNamespace(self):
|
||||
"""
|
||||
Test that the error text element has the correct namespace.
|
||||
"""
|
||||
e = error.StanzaError('feature-not-implemented', text=u'text')
|
||||
element = e.getElement()
|
||||
self.assertEqual(NS_XMPP_STANZAS, element.text.uri)
|
||||
|
||||
|
||||
def test_toResponse(self):
|
||||
"""
|
||||
Test an error response is generated from a stanza.
|
||||
|
||||
The addressing on the (new) response stanza should be reversed, an
|
||||
error child (with proper properties) added and the type set to
|
||||
C{'error'}.
|
||||
"""
|
||||
stanza = domish.Element(('jabber:client', 'message'))
|
||||
stanza['type'] = 'chat'
|
||||
stanza['to'] = 'user1@example.com'
|
||||
stanza['from'] = 'user2@example.com/resource'
|
||||
e = error.StanzaError('service-unavailable')
|
||||
response = e.toResponse(stanza)
|
||||
self.assertNotIdentical(response, stanza)
|
||||
self.assertEqual(response['from'], 'user1@example.com')
|
||||
self.assertEqual(response['to'], 'user2@example.com/resource')
|
||||
self.assertEqual(response['type'], 'error')
|
||||
self.assertEqual(response.error.children[0].name,
|
||||
'service-unavailable')
|
||||
self.assertEqual(response.error['type'], 'cancel')
|
||||
self.assertNotEqual(stanza.children, response.children)
|
||||
|
||||
|
||||
|
||||
class ParseErrorTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{error._parseError}.
|
||||
"""
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.error = domish.Element((None, 'error'))
|
||||
|
||||
|
||||
def test_empty(self):
|
||||
"""
|
||||
Test parsing of the empty error element.
|
||||
"""
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual({'condition': None,
|
||||
'text': None,
|
||||
'textLang': None,
|
||||
'appCondition': None}, result)
|
||||
|
||||
|
||||
def test_condition(self):
|
||||
"""
|
||||
Test parsing of an error element with a condition.
|
||||
"""
|
||||
self.error.addElement(('errorns', 'bad-request'))
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual('bad-request', result['condition'])
|
||||
|
||||
|
||||
def test_text(self):
|
||||
"""
|
||||
Test parsing of an error element with a text.
|
||||
"""
|
||||
text = self.error.addElement(('errorns', 'text'))
|
||||
text.addContent(u'test')
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual('test', result['text'])
|
||||
self.assertEqual(None, result['textLang'])
|
||||
|
||||
|
||||
def test_textLang(self):
|
||||
"""
|
||||
Test parsing of an error element with a text with a defined language.
|
||||
"""
|
||||
text = self.error.addElement(('errorns', 'text'))
|
||||
text[NS_XML, 'lang'] = 'en_US'
|
||||
text.addContent(u'test')
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual('en_US', result['textLang'])
|
||||
|
||||
|
||||
def test_appCondition(self):
|
||||
"""
|
||||
Test parsing of an error element with an app specific condition.
|
||||
"""
|
||||
condition = self.error.addElement(('testns', 'condition'))
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual(condition, result['appCondition'])
|
||||
|
||||
|
||||
def test_appConditionMultiple(self):
|
||||
"""
|
||||
Test parsing of an error element with multiple app specific conditions.
|
||||
"""
|
||||
self.error.addElement(('testns', 'condition'))
|
||||
condition = self.error.addElement(('testns', 'condition2'))
|
||||
result = error._parseError(self.error, 'errorns')
|
||||
self.assertEqual(condition, result['appCondition'])
|
||||
|
||||
|
||||
|
||||
class ExceptionFromStanzaTests(unittest.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
"""
|
||||
Test basic operations of exceptionFromStanza.
|
||||
|
||||
Given a realistic stanza, check if a sane exception is returned.
|
||||
|
||||
Using this stanza::
|
||||
|
||||
<iq type='error'
|
||||
from='pubsub.shakespeare.lit'
|
||||
to='francisco@denmark.lit/barracks'
|
||||
id='subscriptions1'>
|
||||
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
|
||||
<subscriptions/>
|
||||
</pubsub>
|
||||
<error type='cancel'>
|
||||
<feature-not-implemented
|
||||
xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
|
||||
<unsupported xmlns='http://jabber.org/protocol/pubsub#errors'
|
||||
feature='retrieve-subscriptions'/>
|
||||
</error>
|
||||
</iq>
|
||||
"""
|
||||
|
||||
stanza = domish.Element((None, 'stanza'))
|
||||
p = stanza.addElement(('http://jabber.org/protocol/pubsub', 'pubsub'))
|
||||
p.addElement('subscriptions')
|
||||
e = stanza.addElement('error')
|
||||
e['type'] = 'cancel'
|
||||
e.addElement((NS_XMPP_STANZAS, 'feature-not-implemented'))
|
||||
uc = e.addElement(('http://jabber.org/protocol/pubsub#errors',
|
||||
'unsupported'))
|
||||
uc['feature'] = 'retrieve-subscriptions'
|
||||
|
||||
result = error.exceptionFromStanza(stanza)
|
||||
self.assertIsInstance(result, error.StanzaError)
|
||||
self.assertEqual('feature-not-implemented', result.condition)
|
||||
self.assertEqual('cancel', result.type)
|
||||
self.assertEqual(uc, result.appCondition)
|
||||
self.assertEqual([p], result.children)
|
||||
|
||||
def test_legacy(self):
|
||||
"""
|
||||
Test legacy operations of exceptionFromStanza.
|
||||
|
||||
Given a realistic stanza with only legacy (pre-XMPP) error information,
|
||||
check if a sane exception is returned.
|
||||
|
||||
Using this stanza::
|
||||
|
||||
<message type='error'
|
||||
to='piers@pipetree.com/Home'
|
||||
from='qmacro@jaber.org'>
|
||||
<body>Are you there?</body>
|
||||
<error code='502'>Unable to resolve hostname.</error>
|
||||
</message>
|
||||
"""
|
||||
stanza = domish.Element((None, 'stanza'))
|
||||
p = stanza.addElement('body', content=u'Are you there?')
|
||||
e = stanza.addElement('error', content=u'Unable to resolve hostname.')
|
||||
e['code'] = '502'
|
||||
|
||||
result = error.exceptionFromStanza(stanza)
|
||||
self.assertIsInstance(result, error.StanzaError)
|
||||
self.assertEqual('service-unavailable', result.condition)
|
||||
self.assertEqual('wait', result.type)
|
||||
self.assertEqual('Unable to resolve hostname.', result.text)
|
||||
self.assertEqual([p], result.children)
|
||||
|
||||
class ExceptionFromStreamErrorTests(unittest.TestCase):
|
||||
|
||||
def test_basic(self):
|
||||
"""
|
||||
Test basic operations of exceptionFromStreamError.
|
||||
|
||||
Given a realistic stream error, check if a sane exception is returned.
|
||||
|
||||
Using this error::
|
||||
|
||||
<stream:error xmlns:stream='http://etherx.jabber.org/streams'>
|
||||
<xml-not-well-formed xmlns='urn:ietf:params:xml:ns:xmpp-streams'/>
|
||||
</stream:error>
|
||||
"""
|
||||
|
||||
e = domish.Element(('http://etherx.jabber.org/streams', 'error'))
|
||||
e.addElement((NS_XMPP_STREAMS, 'xml-not-well-formed'))
|
||||
|
||||
result = error.exceptionFromStreamError(e)
|
||||
self.assertIsInstance(result, error.StreamError)
|
||||
self.assertEqual('xml-not-well-formed', result.condition)
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.jid}.
|
||||
"""
|
||||
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.protocols.jabber import jid
|
||||
|
||||
class JIDParsingTests(unittest.TestCase):
|
||||
def test_parse(self):
|
||||
"""
|
||||
Test different forms of JIDs.
|
||||
"""
|
||||
# Basic forms
|
||||
self.assertEqual(jid.parse("user@host/resource"),
|
||||
("user", "host", "resource"))
|
||||
self.assertEqual(jid.parse("user@host"),
|
||||
("user", "host", None))
|
||||
self.assertEqual(jid.parse("host"),
|
||||
(None, "host", None))
|
||||
self.assertEqual(jid.parse("host/resource"),
|
||||
(None, "host", "resource"))
|
||||
|
||||
# More interesting forms
|
||||
self.assertEqual(jid.parse("foo/bar@baz"),
|
||||
(None, "foo", "bar@baz"))
|
||||
self.assertEqual(jid.parse("boo@foo/bar@baz"),
|
||||
("boo", "foo", "bar@baz"))
|
||||
self.assertEqual(jid.parse("boo@foo/bar/baz"),
|
||||
("boo", "foo", "bar/baz"))
|
||||
self.assertEqual(jid.parse("boo/foo@bar@baz"),
|
||||
(None, "boo", "foo@bar@baz"))
|
||||
self.assertEqual(jid.parse("boo/foo/bar"),
|
||||
(None, "boo", "foo/bar"))
|
||||
self.assertEqual(jid.parse("boo//foo"),
|
||||
(None, "boo", "/foo"))
|
||||
|
||||
def test_noHost(self):
|
||||
"""
|
||||
Test for failure on no host part.
|
||||
"""
|
||||
self.assertRaises(jid.InvalidFormat, jid.parse, "user@")
|
||||
|
||||
def test_doubleAt(self):
|
||||
"""
|
||||
Test for failure on double @ signs.
|
||||
|
||||
This should fail because @ is not a valid character for the host
|
||||
part of the JID.
|
||||
"""
|
||||
self.assertRaises(jid.InvalidFormat, jid.parse, "user@@host")
|
||||
|
||||
def test_multipleAt(self):
|
||||
"""
|
||||
Test for failure on two @ signs.
|
||||
|
||||
This should fail because @ is not a valid character for the host
|
||||
part of the JID.
|
||||
"""
|
||||
self.assertRaises(jid.InvalidFormat, jid.parse, "user@host@host")
|
||||
|
||||
# Basic tests for case mapping. These are fallback tests for the
|
||||
# prepping done in twisted.words.protocols.jabber.xmpp_stringprep
|
||||
|
||||
def test_prepCaseMapUser(self):
|
||||
"""
|
||||
Test case mapping of the user part of the JID.
|
||||
"""
|
||||
self.assertEqual(jid.prep("UsEr", "host", "resource"),
|
||||
("user", "host", "resource"))
|
||||
|
||||
def test_prepCaseMapHost(self):
|
||||
"""
|
||||
Test case mapping of the host part of the JID.
|
||||
"""
|
||||
self.assertEqual(jid.prep("user", "hoST", "resource"),
|
||||
("user", "host", "resource"))
|
||||
|
||||
def test_prepNoCaseMapResource(self):
|
||||
"""
|
||||
Test no case mapping of the resourcce part of the JID.
|
||||
"""
|
||||
self.assertEqual(jid.prep("user", "hoST", "resource"),
|
||||
("user", "host", "resource"))
|
||||
self.assertNotEqual(jid.prep("user", "host", "Resource"),
|
||||
("user", "host", "resource"))
|
||||
|
||||
class JIDTests(unittest.TestCase):
|
||||
|
||||
def test_noneArguments(self):
|
||||
"""
|
||||
Test that using no arguments raises an exception.
|
||||
"""
|
||||
self.assertRaises(RuntimeError, jid.JID)
|
||||
|
||||
def test_attributes(self):
|
||||
"""
|
||||
Test that the attributes correspond with the JID parts.
|
||||
"""
|
||||
j = jid.JID("user@host/resource")
|
||||
self.assertEqual(j.user, "user")
|
||||
self.assertEqual(j.host, "host")
|
||||
self.assertEqual(j.resource, "resource")
|
||||
|
||||
def test_userhost(self):
|
||||
"""
|
||||
Test the extraction of the bare JID.
|
||||
"""
|
||||
j = jid.JID("user@host/resource")
|
||||
self.assertEqual("user@host", j.userhost())
|
||||
|
||||
def test_userhostOnlyHost(self):
|
||||
"""
|
||||
Test the extraction of the bare JID of the full form host/resource.
|
||||
"""
|
||||
j = jid.JID("host/resource")
|
||||
self.assertEqual("host", j.userhost())
|
||||
|
||||
def test_userhostJID(self):
|
||||
"""
|
||||
Test getting a JID object of the bare JID.
|
||||
"""
|
||||
j1 = jid.JID("user@host/resource")
|
||||
j2 = jid.internJID("user@host")
|
||||
self.assertIdentical(j2, j1.userhostJID())
|
||||
|
||||
def test_userhostJIDNoResource(self):
|
||||
"""
|
||||
Test getting a JID object of the bare JID when there was no resource.
|
||||
"""
|
||||
j = jid.JID("user@host")
|
||||
self.assertIdentical(j, j.userhostJID())
|
||||
|
||||
def test_fullHost(self):
|
||||
"""
|
||||
Test giving a string representation of the JID with only a host part.
|
||||
"""
|
||||
j = jid.JID(tuple=(None, 'host', None))
|
||||
self.assertEqual('host', j.full())
|
||||
|
||||
def test_fullHostResource(self):
|
||||
"""
|
||||
Test giving a string representation of the JID with host, resource.
|
||||
"""
|
||||
j = jid.JID(tuple=(None, 'host', 'resource'))
|
||||
self.assertEqual('host/resource', j.full())
|
||||
|
||||
def test_fullUserHost(self):
|
||||
"""
|
||||
Test giving a string representation of the JID with user, host.
|
||||
"""
|
||||
j = jid.JID(tuple=('user', 'host', None))
|
||||
self.assertEqual('user@host', j.full())
|
||||
|
||||
def test_fullAll(self):
|
||||
"""
|
||||
Test giving a string representation of the JID.
|
||||
"""
|
||||
j = jid.JID(tuple=('user', 'host', 'resource'))
|
||||
self.assertEqual('user@host/resource', j.full())
|
||||
|
||||
def test_equality(self):
|
||||
"""
|
||||
Test JID equality.
|
||||
"""
|
||||
j1 = jid.JID("user@host/resource")
|
||||
j2 = jid.JID("user@host/resource")
|
||||
self.assertNotIdentical(j1, j2)
|
||||
self.assertEqual(j1, j2)
|
||||
|
||||
def test_equalityWithNonJIDs(self):
|
||||
"""
|
||||
Test JID equality.
|
||||
"""
|
||||
j = jid.JID("user@host/resource")
|
||||
self.assertFalse(j == 'user@host/resource')
|
||||
|
||||
def test_inequality(self):
|
||||
"""
|
||||
Test JID inequality.
|
||||
"""
|
||||
j1 = jid.JID("user1@host/resource")
|
||||
j2 = jid.JID("user2@host/resource")
|
||||
self.assertNotEqual(j1, j2)
|
||||
|
||||
def test_inequalityWithNonJIDs(self):
|
||||
"""
|
||||
Test JID equality.
|
||||
"""
|
||||
j = jid.JID("user@host/resource")
|
||||
self.assertNotEqual(j, 'user@host/resource')
|
||||
|
||||
def test_hashable(self):
|
||||
"""
|
||||
Test JID hashability.
|
||||
"""
|
||||
j1 = jid.JID("user@host/resource")
|
||||
j2 = jid.JID("user@host/resource")
|
||||
self.assertEqual(hash(j1), hash(j2))
|
||||
|
||||
def test_unicode(self):
|
||||
"""
|
||||
Test unicode representation of JIDs.
|
||||
"""
|
||||
j = jid.JID(tuple=('user', 'host', 'resource'))
|
||||
self.assertEqual(u"user@host/resource", unicode(j))
|
||||
|
||||
def test_repr(self):
|
||||
"""
|
||||
Test representation of JID objects.
|
||||
"""
|
||||
j = jid.JID(tuple=('user', 'host', 'resource'))
|
||||
self.assertEqual("JID(%s)" % repr(u'user@host/resource'), repr(j))
|
||||
|
||||
class InternJIDTests(unittest.TestCase):
|
||||
def test_identity(self):
|
||||
"""
|
||||
Test that two interned JIDs yield the same object.
|
||||
"""
|
||||
j1 = jid.internJID("user@host")
|
||||
j2 = jid.internJID("user@host")
|
||||
self.assertIdentical(j1, j2)
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.jstrports}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.protocols.jabber import jstrports
|
||||
from twisted.application.internet import TCPClient
|
||||
|
||||
|
||||
class JabberStrPortsPlaceHolderTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{jstrports}
|
||||
"""
|
||||
|
||||
def test_parse(self):
|
||||
"""
|
||||
L{jstrports.parse} accepts an endpoint description string and returns a
|
||||
tuple and dict of parsed endpoint arguments.
|
||||
"""
|
||||
expected = ('TCP', ('DOMAIN', 65535, 'Factory'), {})
|
||||
got = jstrports.parse("tcp:DOMAIN:65535", "Factory")
|
||||
self.assertEqual(expected, got)
|
||||
|
||||
|
||||
def test_client(self):
|
||||
"""
|
||||
L{jstrports.client} returns a L{TCPClient} service.
|
||||
"""
|
||||
got = jstrports.client("tcp:DOMAIN:65535", "Factory")
|
||||
self.assertIsInstance(got, TCPClient)
|
||||
|
|
@ -0,0 +1,292 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.protocols.jabber import sasl, sasl_mechanisms, xmlstream, jid
|
||||
from twisted.words.xish import domish
|
||||
|
||||
NS_XMPP_SASL = 'urn:ietf:params:xml:ns:xmpp-sasl'
|
||||
|
||||
@implementer(sasl_mechanisms.ISASLMechanism)
|
||||
class DummySASLMechanism(object):
|
||||
"""
|
||||
Dummy SASL mechanism.
|
||||
|
||||
This just returns the initialResponse passed on creation, stores any
|
||||
challenges and replies with the value of C{response}.
|
||||
|
||||
@ivar challenge: Last received challenge.
|
||||
@type challenge: C{unicode}.
|
||||
@ivar initialResponse: Initial response to be returned when requested
|
||||
via C{getInitialResponse} or L{None}.
|
||||
@type initialResponse: C{unicode}
|
||||
"""
|
||||
|
||||
challenge = None
|
||||
name = u"DUMMY"
|
||||
response = b""
|
||||
|
||||
def __init__(self, initialResponse):
|
||||
self.initialResponse = initialResponse
|
||||
|
||||
def getInitialResponse(self):
|
||||
return self.initialResponse
|
||||
|
||||
def getResponse(self, challenge):
|
||||
self.challenge = challenge
|
||||
return self.response
|
||||
|
||||
|
||||
class DummySASLInitiatingInitializer(sasl.SASLInitiatingInitializer):
|
||||
"""
|
||||
Dummy SASL Initializer for initiating entities.
|
||||
|
||||
This hardwires the SASL mechanism to L{DummySASLMechanism}, that is
|
||||
instantiated with the value of C{initialResponse}.
|
||||
|
||||
@ivar initialResponse: The initial response to be returned by the
|
||||
dummy SASL mechanism or L{None}.
|
||||
@type initialResponse: C{unicode}.
|
||||
"""
|
||||
|
||||
initialResponse = None
|
||||
|
||||
def setMechanism(self):
|
||||
self.mechanism = DummySASLMechanism(self.initialResponse)
|
||||
|
||||
|
||||
|
||||
class SASLInitiatingInitializerTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{sasl.SASLInitiatingInitializer}
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.output = []
|
||||
|
||||
self.authenticator = xmlstream.Authenticator()
|
||||
self.xmlstream = xmlstream.XmlStream(self.authenticator)
|
||||
self.xmlstream.send = self.output.append
|
||||
self.xmlstream.connectionMade()
|
||||
self.xmlstream.dataReceived(b"<stream:stream xmlns='jabber:client' "
|
||||
b"xmlns:stream='http://etherx.jabber.org/streams' "
|
||||
b"from='example.com' id='12345' version='1.0'>")
|
||||
self.init = DummySASLInitiatingInitializer(self.xmlstream)
|
||||
|
||||
|
||||
def test_onFailure(self):
|
||||
"""
|
||||
Test that the SASL error condition is correctly extracted.
|
||||
"""
|
||||
failure = domish.Element(('urn:ietf:params:xml:ns:xmpp-sasl',
|
||||
'failure'))
|
||||
failure.addElement('not-authorized')
|
||||
self.init._deferred = defer.Deferred()
|
||||
self.init.onFailure(failure)
|
||||
self.assertFailure(self.init._deferred, sasl.SASLAuthError)
|
||||
self.init._deferred.addCallback(lambda e:
|
||||
self.assertEqual('not-authorized',
|
||||
e.condition))
|
||||
return self.init._deferred
|
||||
|
||||
|
||||
def test_sendAuthInitialResponse(self):
|
||||
"""
|
||||
Test starting authentication with an initial response.
|
||||
"""
|
||||
self.init.initialResponse = b"dummy"
|
||||
self.init.start()
|
||||
auth = self.output[0]
|
||||
self.assertEqual(NS_XMPP_SASL, auth.uri)
|
||||
self.assertEqual(u'auth', auth.name)
|
||||
self.assertEqual(u'DUMMY', auth['mechanism'])
|
||||
self.assertEqual(u'ZHVtbXk=', unicode(auth))
|
||||
|
||||
|
||||
def test_sendAuthNoInitialResponse(self):
|
||||
"""
|
||||
Test starting authentication without an initial response.
|
||||
"""
|
||||
self.init.initialResponse = None
|
||||
self.init.start()
|
||||
auth = self.output[0]
|
||||
self.assertEqual(u'', str(auth))
|
||||
|
||||
|
||||
def test_sendAuthEmptyInitialResponse(self):
|
||||
"""
|
||||
Test starting authentication where the initial response is empty.
|
||||
"""
|
||||
self.init.initialResponse = b""
|
||||
self.init.start()
|
||||
auth = self.output[0]
|
||||
self.assertEqual('=', unicode(auth))
|
||||
|
||||
|
||||
def test_onChallenge(self):
|
||||
"""
|
||||
Test receiving a challenge message.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
challenge.addContent(u'bXkgY2hhbGxlbmdl')
|
||||
self.init.onChallenge(challenge)
|
||||
self.assertEqual(b'my challenge', self.init.mechanism.challenge)
|
||||
self.init.onSuccess(None)
|
||||
return d
|
||||
|
||||
|
||||
def test_onChallengeResponse(self):
|
||||
"""
|
||||
A non-empty response gets encoded and included as character data.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
challenge.addContent(u'bXkgY2hhbGxlbmdl')
|
||||
self.init.mechanism.response = b"response"
|
||||
self.init.onChallenge(challenge)
|
||||
response = self.output[1]
|
||||
self.assertEqual(u'cmVzcG9uc2U=', unicode(response))
|
||||
self.init.onSuccess(None)
|
||||
return d
|
||||
|
||||
|
||||
def test_onChallengeEmpty(self):
|
||||
"""
|
||||
Test receiving an empty challenge message.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
self.init.onChallenge(challenge)
|
||||
self.assertEqual(b'', self.init.mechanism.challenge)
|
||||
self.init.onSuccess(None)
|
||||
return d
|
||||
|
||||
|
||||
def test_onChallengeIllegalPadding(self):
|
||||
"""
|
||||
Test receiving a challenge message with illegal padding.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
challenge.addContent(u'bXkg=Y2hhbGxlbmdl')
|
||||
self.init.onChallenge(challenge)
|
||||
self.assertFailure(d, sasl.SASLIncorrectEncodingError)
|
||||
return d
|
||||
|
||||
|
||||
def test_onChallengeIllegalCharacters(self):
|
||||
"""
|
||||
Test receiving a challenge message with illegal characters.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
challenge.addContent(u'bXkg*Y2hhbGxlbmdl')
|
||||
self.init.onChallenge(challenge)
|
||||
self.assertFailure(d, sasl.SASLIncorrectEncodingError)
|
||||
return d
|
||||
|
||||
|
||||
def test_onChallengeMalformed(self):
|
||||
"""
|
||||
Test receiving a malformed challenge message.
|
||||
"""
|
||||
d = self.init.start()
|
||||
challenge = domish.Element((NS_XMPP_SASL, 'challenge'))
|
||||
challenge.addContent(u'a')
|
||||
self.init.onChallenge(challenge)
|
||||
self.assertFailure(d, sasl.SASLIncorrectEncodingError)
|
||||
return d
|
||||
|
||||
|
||||
class SASLInitiatingInitializerSetMechanismTests(unittest.TestCase):
|
||||
"""
|
||||
Test for L{sasl.SASLInitiatingInitializer.setMechanism}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.output = []
|
||||
|
||||
self.authenticator = xmlstream.Authenticator()
|
||||
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 = sasl.SASLInitiatingInitializer(self.xmlstream)
|
||||
|
||||
|
||||
def _setMechanism(self, name):
|
||||
"""
|
||||
Set up the XML Stream to have a SASL feature with the given mechanism.
|
||||
"""
|
||||
feature = domish.Element((NS_XMPP_SASL, 'mechanisms'))
|
||||
feature.addElement('mechanism', content=name)
|
||||
self.xmlstream.features[(feature.uri, feature.name)] = feature
|
||||
|
||||
self.init.setMechanism()
|
||||
return self.init.mechanism.name
|
||||
|
||||
|
||||
def test_anonymous(self):
|
||||
"""
|
||||
Test setting ANONYMOUS as the authentication mechanism.
|
||||
"""
|
||||
self.authenticator.jid = jid.JID('example.com')
|
||||
self.authenticator.password = None
|
||||
name = u"ANONYMOUS"
|
||||
|
||||
self.assertEqual(name, self._setMechanism(name))
|
||||
|
||||
|
||||
def test_plain(self):
|
||||
"""
|
||||
Test setting PLAIN as the authentication mechanism.
|
||||
"""
|
||||
self.authenticator.jid = jid.JID('test@example.com')
|
||||
self.authenticator.password = 'secret'
|
||||
name = u"PLAIN"
|
||||
|
||||
self.assertEqual(name, self._setMechanism(name))
|
||||
|
||||
|
||||
def test_digest(self):
|
||||
"""
|
||||
Test setting DIGEST-MD5 as the authentication mechanism.
|
||||
"""
|
||||
self.authenticator.jid = jid.JID('test@example.com')
|
||||
self.authenticator.password = 'secret'
|
||||
name = u"DIGEST-MD5"
|
||||
|
||||
self.assertEqual(name, self._setMechanism(name))
|
||||
|
||||
|
||||
def test_notAcceptable(self):
|
||||
"""
|
||||
Test using an unacceptable SASL authentication mechanism.
|
||||
"""
|
||||
|
||||
self.authenticator.jid = jid.JID('test@example.com')
|
||||
self.authenticator.password = u'secret'
|
||||
|
||||
self.assertRaises(sasl.SASLNoAcceptableMechanism,
|
||||
self._setMechanism, u'SOMETHING_UNACCEPTABLE')
|
||||
|
||||
|
||||
def test_notAcceptableWithoutUser(self):
|
||||
"""
|
||||
Test using an unacceptable SASL authentication mechanism with no JID.
|
||||
"""
|
||||
self.authenticator.jid = jid.JID('example.com')
|
||||
self.authenticator.password = u'secret'
|
||||
|
||||
self.assertRaises(sasl.SASLNoAcceptableMechanism,
|
||||
self._setMechanism, u'SOMETHING_UNACCEPTABLE')
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.sasl_mechanisms}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.python.compat import networkString
|
||||
from twisted.words.protocols.jabber import sasl_mechanisms
|
||||
|
||||
class PlainTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.Plain}.
|
||||
"""
|
||||
def test_getInitialResponse(self):
|
||||
"""
|
||||
Test the initial response.
|
||||
"""
|
||||
m = sasl_mechanisms.Plain(None, u'test', u'secret')
|
||||
self.assertEqual(m.getInitialResponse(), b'\x00test\x00secret')
|
||||
|
||||
|
||||
|
||||
class AnonymousTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.Anonymous}.
|
||||
"""
|
||||
def test_getInitialResponse(self):
|
||||
"""
|
||||
Test the initial response to be empty.
|
||||
"""
|
||||
m = sasl_mechanisms.Anonymous()
|
||||
self.assertEqual(m.getInitialResponse(), None)
|
||||
|
||||
|
||||
|
||||
class DigestMD5Tests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{twisted.words.protocols.jabber.sasl_mechanisms.DigestMD5}.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.mechanism = sasl_mechanisms.DigestMD5(
|
||||
u'xmpp', u'example.org', None, u'test', u'secret')
|
||||
|
||||
|
||||
def test_getInitialResponse(self):
|
||||
"""
|
||||
Test that no initial response is generated.
|
||||
"""
|
||||
self.assertIdentical(self.mechanism.getInitialResponse(), None)
|
||||
|
||||
|
||||
def test_getResponse(self):
|
||||
"""
|
||||
The response to a Digest-MD5 challenge includes the parameters from the
|
||||
challenge.
|
||||
"""
|
||||
challenge = (
|
||||
b'realm="localhost",nonce="1234",qop="auth",charset=utf-8,'
|
||||
b'algorithm=md5-sess')
|
||||
directives = self.mechanism._parse(
|
||||
self.mechanism.getResponse(challenge))
|
||||
del directives[b"cnonce"], directives[b"response"]
|
||||
self.assertEqual({
|
||||
b'username': b'test', b'nonce': b'1234', b'nc': b'00000001',
|
||||
b'qop': [b'auth'], b'charset': b'utf-8',
|
||||
b'realm': b'localhost', b'digest-uri': b'xmpp/example.org'
|
||||
}, directives)
|
||||
|
||||
|
||||
def test_getResponseNonAsciiRealm(self):
|
||||
"""
|
||||
Bytes outside the ASCII range in the challenge are nevertheless
|
||||
included in the response.
|
||||
"""
|
||||
challenge = (b'realm="\xc3\xa9chec.example.org",nonce="1234",'
|
||||
b'qop="auth",charset=utf-8,algorithm=md5-sess')
|
||||
directives = self.mechanism._parse(
|
||||
self.mechanism.getResponse(challenge))
|
||||
del directives[b"cnonce"], directives[b"response"]
|
||||
self.assertEqual({
|
||||
b'username': b'test', b'nonce': b'1234', b'nc': b'00000001',
|
||||
b'qop': [b'auth'], b'charset': b'utf-8',
|
||||
b'realm': b'\xc3\xa9chec.example.org',
|
||||
b'digest-uri': b'xmpp/example.org'}, directives)
|
||||
|
||||
|
||||
def test_getResponseNoRealm(self):
|
||||
"""
|
||||
The response to a challenge without a realm uses the host part of the
|
||||
JID as the realm.
|
||||
"""
|
||||
challenge = b'nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
|
||||
directives = self.mechanism._parse(
|
||||
self.mechanism.getResponse(challenge))
|
||||
self.assertEqual(directives[b'realm'], b'example.org')
|
||||
|
||||
|
||||
def test_getResponseNoRealmIDN(self):
|
||||
"""
|
||||
If the challenge does not include a realm and the host part of the JID
|
||||
includes bytes outside of the ASCII range, the response still includes
|
||||
the host part of the JID as the realm.
|
||||
"""
|
||||
self.mechanism = sasl_mechanisms.DigestMD5(
|
||||
u'xmpp', u'\u00e9chec.example.org', None, u'test', u'secret')
|
||||
challenge = b'nonce="1234",qop="auth",charset=utf-8,algorithm=md5-sess'
|
||||
directives = self.mechanism._parse(
|
||||
self.mechanism.getResponse(challenge))
|
||||
self.assertEqual(directives[b'realm'], b'\xc3\xa9chec.example.org')
|
||||
|
||||
|
||||
def test_getResponseRspauth(self):
|
||||
"""
|
||||
If the challenge just has a rspauth directive, the response is empty.
|
||||
"""
|
||||
challenge = \
|
||||
b'rspauth=cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA=='
|
||||
response = self.mechanism.getResponse(challenge)
|
||||
self.assertEqual(b"", response)
|
||||
|
||||
|
||||
def test_calculateResponse(self):
|
||||
"""
|
||||
The response to a Digest-MD5 challenge is computed according to RFC
|
||||
2831.
|
||||
"""
|
||||
charset = 'utf-8'
|
||||
nonce = b'OA6MG9tEQGm2hh'
|
||||
nc = networkString('%08x' % (1,))
|
||||
cnonce = b'OA6MHXh6VqTrRk'
|
||||
|
||||
username = u'\u0418chris'
|
||||
password = u'\u0418secret'
|
||||
host = u'\u0418elwood.innosoft.com'
|
||||
digestURI = u'imap/\u0418elwood.innosoft.com'.encode(charset)
|
||||
|
||||
mechanism = sasl_mechanisms.DigestMD5(
|
||||
b'imap', host, None, username, password)
|
||||
response = mechanism._calculateResponse(
|
||||
cnonce, nc, nonce, username.encode(charset),
|
||||
password.encode(charset), host.encode(charset), digestURI)
|
||||
self.assertEqual(response, b'7928f233258be88392424d094453c5e3')
|
||||
|
||||
|
||||
def test_parse(self):
|
||||
"""
|
||||
A challenge can be parsed into a L{dict} with L{bytes} or L{list}
|
||||
values.
|
||||
"""
|
||||
challenge = (
|
||||
b'nonce="1234",qop="auth,auth-conf",charset=utf-8,'
|
||||
b'algorithm=md5-sess,cipher="des,3des"')
|
||||
directives = self.mechanism._parse(challenge)
|
||||
self.assertEqual({
|
||||
b"algorithm": b"md5-sess", b"nonce": b"1234",
|
||||
b"charset": b"utf-8", b"qop": [b'auth', b'auth-conf'],
|
||||
b"cipher": [b'des', b'3des']
|
||||
}, directives)
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,115 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.protocols.jabber.xmpp_stringprep import (
|
||||
nodeprep, resourceprep, nameprep)
|
||||
|
||||
|
||||
|
||||
class DeprecationTests(unittest.TestCase):
|
||||
"""
|
||||
Deprecations in L{twisted.words.protocols.jabber.xmpp_stringprep}.
|
||||
"""
|
||||
def test_crippled(self):
|
||||
"""
|
||||
L{xmpp_stringprep.crippled} is deprecated and always returns C{False}.
|
||||
"""
|
||||
from twisted.words.protocols.jabber.xmpp_stringprep import crippled
|
||||
warnings = self.flushWarnings(
|
||||
offendingFunctions=[self.test_crippled])
|
||||
self.assertEqual(DeprecationWarning, warnings[0]['category'])
|
||||
self.assertEqual(
|
||||
"twisted.words.protocols.jabber.xmpp_stringprep.crippled was "
|
||||
"deprecated in Twisted 13.1.0: crippled is always False",
|
||||
warnings[0]['message'])
|
||||
self.assertEqual(1, len(warnings))
|
||||
self.assertEqual(crippled, False)
|
||||
|
||||
|
||||
|
||||
class XMPPStringPrepTests(unittest.TestCase):
|
||||
"""
|
||||
The nodeprep stringprep profile is similar to the resourceprep profile,
|
||||
but does an extra mapping of characters (table B.2) and disallows
|
||||
more characters (table C.1.1 and eight extra punctuation characters).
|
||||
Due to this similarity, the resourceprep tests are more extensive, and
|
||||
the nodeprep tests only address the mappings additional restrictions.
|
||||
|
||||
The nameprep profile is nearly identical to the nameprep implementation in
|
||||
L{encodings.idna}, but that implementation assumes the C{UseSTD4ASCIIRules}
|
||||
flag to be false. This implementation assumes it to be true, and restricts
|
||||
the allowed set of characters. The tests here only check for the
|
||||
differences.
|
||||
"""
|
||||
|
||||
def testResourcePrep(self):
|
||||
self.assertEqual(resourceprep.prepare(u'resource'), u'resource')
|
||||
self.assertNotEqual(resourceprep.prepare(u'Resource'), u'resource')
|
||||
self.assertEqual(resourceprep.prepare(u' '), u' ')
|
||||
|
||||
self.assertEqual(resourceprep.prepare(u'Henry \u2163'), u'Henry IV')
|
||||
self.assertEqual(resourceprep.prepare(u'foo\xad\u034f\u1806\u180b'
|
||||
u'bar\u200b\u2060'
|
||||
u'baz\ufe00\ufe08\ufe0f\ufeff'),
|
||||
u'foobarbaz')
|
||||
self.assertEqual(resourceprep.prepare(u'\u00a0'), u' ')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u1680')
|
||||
self.assertEqual(resourceprep.prepare(u'\u2000'), u' ')
|
||||
self.assertEqual(resourceprep.prepare(u'\u200b'), u'')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u0010\u007f')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u0085')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u180e')
|
||||
self.assertEqual(resourceprep.prepare(u'\ufeff'), u'')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\uf123')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000f1234')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0010f234')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0008fffe')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U0010ffff')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\udf42')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\ufffd')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u2ff5')
|
||||
self.assertEqual(resourceprep.prepare(u'\u0341'), u'\u0301')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u200e')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u202a')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0001')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0042')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'foo\u05bebar')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'foo\ufd50bar')
|
||||
#self.assertEqual(resourceprep.prepare(u'foo\ufb38bar'),
|
||||
# u'foo\u064ebar')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\u06271')
|
||||
self.assertEqual(resourceprep.prepare(u'\u06271\u0628'),
|
||||
u'\u06271\u0628')
|
||||
self.assertRaises(UnicodeError, resourceprep.prepare, u'\U000e0002')
|
||||
|
||||
|
||||
def testNodePrep(self):
|
||||
self.assertEqual(nodeprep.prepare(u'user'), u'user')
|
||||
self.assertEqual(nodeprep.prepare(u'User'), u'user')
|
||||
self.assertRaises(UnicodeError, nodeprep.prepare, u'us&er')
|
||||
|
||||
|
||||
def test_nodeprepUnassignedInUnicode32(self):
|
||||
"""
|
||||
Make sure unassigned code points from Unicode 3.2 are rejected.
|
||||
"""
|
||||
self.assertRaises(UnicodeError, nodeprep.prepare, u'\u1d39')
|
||||
|
||||
|
||||
def testNamePrep(self):
|
||||
self.assertEqual(nameprep.prepare(u'example.com'), u'example.com')
|
||||
self.assertEqual(nameprep.prepare(u'Example.com'), u'example.com')
|
||||
self.assertRaises(UnicodeError, nameprep.prepare, u'ex@mple.com')
|
||||
self.assertRaises(UnicodeError, nameprep.prepare, u'-example.com')
|
||||
self.assertRaises(UnicodeError, nameprep.prepare, u'example-.com')
|
||||
|
||||
self.assertEqual(nameprep.prepare(u'stra\u00dfe.example.com'),
|
||||
u'strasse.example.com')
|
||||
|
||||
def test_nameprepTrailingDot(self):
|
||||
"""
|
||||
A trailing dot in domain names is preserved.
|
||||
"""
|
||||
self.assertEqual(nameprep.prepare(u'example.com.'), u'example.com.')
|
||||
|
|
@ -0,0 +1,843 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.service}.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from twisted.cred import portal, credentials, checkers
|
||||
from twisted.internet import address, defer, reactor
|
||||
from twisted.internet.defer import Deferred, DeferredList, maybeDeferred, succeed
|
||||
from twisted.python.compat import unicode
|
||||
from twisted.spread import pb
|
||||
from twisted.test import proto_helpers
|
||||
from twisted.trial import unittest
|
||||
from twisted.words import ewords, service
|
||||
from twisted.words.protocols import irc
|
||||
|
||||
class RealmTests(unittest.TestCase):
|
||||
def _entityCreationTest(self, kind):
|
||||
# Kind is "user" or "group"
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
name = u'test' + kind.lower()
|
||||
create = getattr(realm, 'create' + kind.title())
|
||||
get = getattr(realm, 'get' + kind.title())
|
||||
flag = 'create' + kind.title() + 'OnRequest'
|
||||
dupExc = getattr(ewords, 'Duplicate' + kind.title())
|
||||
noSuchExc = getattr(ewords, 'NoSuch' + kind.title())
|
||||
|
||||
# Creating should succeed
|
||||
p = self.successResultOf(create(name))
|
||||
self.assertEqual(name, p.name)
|
||||
|
||||
# Creating the same user again should not
|
||||
self.failureResultOf(create(name)).trap(dupExc)
|
||||
|
||||
# Getting a non-existent user should succeed if createUserOnRequest is True
|
||||
setattr(realm, flag, True)
|
||||
p = self.successResultOf(get(u"new" + kind.lower()))
|
||||
self.assertEqual("new" + kind.lower(), p.name)
|
||||
|
||||
# Getting that user again should return the same object
|
||||
newp = self.successResultOf(get(u"new" + kind.lower()))
|
||||
self.assertIdentical(p, newp)
|
||||
|
||||
# Getting a non-existent user should fail if createUserOnRequest is False
|
||||
setattr(realm, flag, False)
|
||||
self.failureResultOf(get(u"another" + kind.lower())).trap(noSuchExc)
|
||||
|
||||
|
||||
def testUserCreation(self):
|
||||
return self._entityCreationTest("User")
|
||||
|
||||
|
||||
def testGroupCreation(self):
|
||||
return self._entityCreationTest("Group")
|
||||
|
||||
|
||||
def testUserRetrieval(self):
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
# Make a user to play around with
|
||||
user = self.successResultOf(realm.createUser(u"testuser"))
|
||||
|
||||
# Make sure getting the user returns the same object
|
||||
retrieved = self.successResultOf(realm.getUser(u"testuser"))
|
||||
self.assertIdentical(user, retrieved)
|
||||
|
||||
# Make sure looking up the user also returns the same object
|
||||
lookedUp = self.successResultOf(realm.lookupUser(u"testuser"))
|
||||
self.assertIdentical(retrieved, lookedUp)
|
||||
|
||||
# Make sure looking up a user who does not exist fails
|
||||
(self.failureResultOf(realm.lookupUser(u"nosuchuser"))
|
||||
.trap(ewords.NoSuchUser))
|
||||
|
||||
|
||||
def testUserAddition(self):
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
# Create and manually add a user to the realm
|
||||
p = service.User("testuser")
|
||||
user = self.successResultOf(realm.addUser(p))
|
||||
self.assertIdentical(p, user)
|
||||
|
||||
# Make sure getting that user returns the same object
|
||||
retrieved = self.successResultOf(realm.getUser(u"testuser"))
|
||||
self.assertIdentical(user, retrieved)
|
||||
|
||||
# Make sure looking up that user returns the same object
|
||||
lookedUp = self.successResultOf(realm.lookupUser(u"testuser"))
|
||||
self.assertIdentical(retrieved, lookedUp)
|
||||
|
||||
|
||||
def testGroupRetrieval(self):
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
group = self.successResultOf(realm.createGroup(u"testgroup"))
|
||||
|
||||
retrieved = self.successResultOf(realm.getGroup(u"testgroup"))
|
||||
|
||||
self.assertIdentical(group, retrieved)
|
||||
|
||||
(self.failureResultOf(realm.getGroup(u"nosuchgroup"))
|
||||
.trap(ewords.NoSuchGroup))
|
||||
|
||||
|
||||
def testGroupAddition(self):
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
p = service.Group("testgroup")
|
||||
self.successResultOf(realm.addGroup(p))
|
||||
group = self.successResultOf(realm.getGroup(u"testGroup"))
|
||||
self.assertIdentical(p, group)
|
||||
|
||||
|
||||
def testGroupUsernameCollision(self):
|
||||
"""
|
||||
Try creating a group with the same name as an existing user and
|
||||
assert that it succeeds, since users and groups should not be in the
|
||||
same namespace and collisions should be impossible.
|
||||
"""
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
|
||||
self.successResultOf(realm.createUser(u"test"))
|
||||
self.successResultOf(realm.createGroup(u"test"))
|
||||
|
||||
|
||||
def testEnumeration(self):
|
||||
realm = service.InMemoryWordsRealm("realmname")
|
||||
self.successResultOf(realm.createGroup(u"groupone"))
|
||||
|
||||
self.successResultOf(realm.createGroup(u"grouptwo"))
|
||||
|
||||
groups = self.successResultOf(realm.itergroups())
|
||||
|
||||
n = [g.name for g in groups]
|
||||
n.sort()
|
||||
self.assertEqual(n, ["groupone", "grouptwo"])
|
||||
|
||||
|
||||
|
||||
class TestCaseUserAgg(object):
|
||||
def __init__(self, user, realm, factory, address=address.IPv4Address('TCP', '127.0.0.1', 54321)):
|
||||
self.user = user
|
||||
self.transport = proto_helpers.StringTransportWithDisconnection()
|
||||
self.protocol = factory.buildProtocol(address)
|
||||
self.transport.protocol = self.protocol
|
||||
self.user.mind = self.protocol
|
||||
self.protocol.makeConnection(self.transport)
|
||||
|
||||
|
||||
def write(self, stuff):
|
||||
self.protocol.dataReceived(stuff)
|
||||
|
||||
|
||||
class IRCProtocolTests(unittest.TestCase):
|
||||
STATIC_USERS = [
|
||||
u'useruser', u'otheruser', u'someguy', u'firstuser', u'username',
|
||||
u'userone', u'usertwo', u'userthree', 'userfour', b'userfive', u'someuser']
|
||||
|
||||
|
||||
def setUp(self):
|
||||
self.realm = service.InMemoryWordsRealm("realmname")
|
||||
self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
|
||||
self.portal = portal.Portal(self.realm, [self.checker])
|
||||
self.factory = service.IRCFactory(self.realm, self.portal)
|
||||
|
||||
c = []
|
||||
for nick in self.STATIC_USERS:
|
||||
if isinstance(nick, bytes):
|
||||
nick = nick.decode("utf-8")
|
||||
c.append(self.realm.createUser(nick))
|
||||
self.checker.addUser(nick, nick + u"_password")
|
||||
return DeferredList(c)
|
||||
|
||||
|
||||
def _assertGreeting(self, user):
|
||||
"""
|
||||
The user has been greeted with the four messages that are (usually)
|
||||
considered to start an IRC session.
|
||||
|
||||
Asserts that the required responses were received.
|
||||
"""
|
||||
# Make sure we get 1-4 at least
|
||||
response = self._response(user)
|
||||
expected = [irc.RPL_WELCOME, irc.RPL_YOURHOST, irc.RPL_CREATED,
|
||||
irc.RPL_MYINFO]
|
||||
for (prefix, command, args) in response:
|
||||
if command in expected:
|
||||
expected.remove(command)
|
||||
self.assertFalse(expected, "Missing responses for %r" % (expected,))
|
||||
|
||||
|
||||
def _login(self, user, nick, password=None):
|
||||
if password is None:
|
||||
password = nick + "_password"
|
||||
user.write(u'PASS %s\r\n' % (password,))
|
||||
user.write(u'NICK %s extrainfo\r\n' % (nick,))
|
||||
|
||||
|
||||
def _loggedInUser(self, name):
|
||||
user = self.successResultOf(self.realm.lookupUser(name))
|
||||
agg = TestCaseUserAgg(user, self.realm, self.factory)
|
||||
self._login(agg, name)
|
||||
return agg
|
||||
|
||||
|
||||
def _response(self, user, messageType=None):
|
||||
"""
|
||||
Extracts the user's response, and returns a list of parsed lines.
|
||||
If messageType is defined, only messages of that type will be returned.
|
||||
"""
|
||||
response = user.transport.value()
|
||||
if bytes != str and isinstance(response, bytes):
|
||||
response = response.decode("utf-8")
|
||||
response = response.splitlines()
|
||||
user.transport.clear()
|
||||
result = []
|
||||
for message in map(irc.parsemsg, response):
|
||||
if messageType is None or message[1] == messageType:
|
||||
result.append(message)
|
||||
return result
|
||||
|
||||
|
||||
def testPASSLogin(self):
|
||||
user = self._loggedInUser(u'firstuser')
|
||||
self._assertGreeting(user)
|
||||
|
||||
|
||||
def test_nickServLogin(self):
|
||||
"""
|
||||
Sending NICK without PASS will prompt the user for their password.
|
||||
When the user sends their password to NickServ, it will respond with a
|
||||
Greeting.
|
||||
"""
|
||||
firstuser = self.successResultOf(self.realm.lookupUser(u'firstuser'))
|
||||
|
||||
user = TestCaseUserAgg(firstuser, self.realm, self.factory)
|
||||
user.write('NICK firstuser extrainfo\r\n')
|
||||
response = self._response(user, 'PRIVMSG')
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], service.NICKSERV)
|
||||
self.assertEqual(response[0][1], 'PRIVMSG')
|
||||
self.assertEqual(response[0][2], ['firstuser', 'Password?'])
|
||||
user.transport.clear()
|
||||
|
||||
user.write('PRIVMSG nickserv firstuser_password\r\n')
|
||||
self._assertGreeting(user)
|
||||
|
||||
|
||||
def testFailedLogin(self):
|
||||
firstuser = self.successResultOf(self.realm.lookupUser(u'firstuser'))
|
||||
|
||||
user = TestCaseUserAgg(firstuser, self.realm, self.factory)
|
||||
self._login(user, u"firstuser", u"wrongpass")
|
||||
response = self._response(user, "PRIVMSG")
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][2], ['firstuser', 'Login failed. Goodbye.'])
|
||||
|
||||
|
||||
def testLogout(self):
|
||||
logout = []
|
||||
firstuser = self.successResultOf(self.realm.lookupUser(u'firstuser'))
|
||||
|
||||
user = TestCaseUserAgg(firstuser, self.realm, self.factory)
|
||||
self._login(user, "firstuser")
|
||||
user.protocol.logout = lambda: logout.append(True)
|
||||
user.write('QUIT\r\n')
|
||||
self.assertEqual(logout, [True])
|
||||
|
||||
|
||||
def testJoin(self):
|
||||
firstuser = self.successResultOf(self.realm.lookupUser(u'firstuser'))
|
||||
|
||||
somechannel = self.successResultOf(
|
||||
self.realm.createGroup(u"somechannel"))
|
||||
|
||||
somechannel.meta['topic'] = 'some random topic'
|
||||
|
||||
# Bring in one user, make sure he gets into the channel sanely
|
||||
user = TestCaseUserAgg(firstuser, self.realm, self.factory)
|
||||
self._login(user, "firstuser")
|
||||
user.transport.clear()
|
||||
user.write('JOIN #somechannel\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
self.assertEqual(len(response), 5)
|
||||
|
||||
# Join message
|
||||
self.assertEqual(response[0][0], 'firstuser!firstuser@realmname')
|
||||
self.assertEqual(response[0][1], 'JOIN')
|
||||
self.assertEqual(response[0][2], ['#somechannel'])
|
||||
|
||||
# User list
|
||||
self.assertEqual(response[1][1], '353')
|
||||
self.assertEqual(response[2][1], '366')
|
||||
|
||||
# Topic (or lack thereof, as the case may be)
|
||||
self.assertEqual(response[3][1], '332')
|
||||
self.assertEqual(response[4][1], '333')
|
||||
|
||||
|
||||
# Hook up another client! It is a CHAT SYSTEM!!!!!!!
|
||||
other = self._loggedInUser(u'otheruser')
|
||||
|
||||
other.transport.clear()
|
||||
user.transport.clear()
|
||||
other.write('JOIN #somechannel\r\n')
|
||||
|
||||
# At this point, both users should be in the channel
|
||||
response = self._response(other)
|
||||
|
||||
event = self._response(user)
|
||||
self.assertEqual(len(event), 1)
|
||||
self.assertEqual(event[0][0], 'otheruser!otheruser@realmname')
|
||||
self.assertEqual(event[0][1], 'JOIN')
|
||||
self.assertEqual(event[0][2], ['#somechannel'])
|
||||
|
||||
self.assertEqual(response[1][0], 'realmname')
|
||||
self.assertEqual(response[1][1], '353')
|
||||
self.assertIn(response[1][2], [
|
||||
['otheruser', '=', '#somechannel', 'firstuser otheruser'],
|
||||
['otheruser', '=', '#somechannel', 'otheruser firstuser'],
|
||||
])
|
||||
|
||||
|
||||
def test_joinTopicless(self):
|
||||
"""
|
||||
When a user joins a group without a topic, no topic information is
|
||||
sent to that user.
|
||||
"""
|
||||
firstuser = self.successResultOf(self.realm.lookupUser(u'firstuser'))
|
||||
|
||||
self.successResultOf(self.realm.createGroup(u"somechannel"))
|
||||
|
||||
# Bring in one user, make sure he gets into the channel sanely
|
||||
user = TestCaseUserAgg(firstuser, self.realm, self.factory)
|
||||
self._login(user, "firstuser")
|
||||
user.transport.clear()
|
||||
user.write('JOIN #somechannel\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
responseCodes = [r[1] for r in response]
|
||||
self.assertNotIn('332', responseCodes)
|
||||
self.assertNotIn('333', responseCodes)
|
||||
|
||||
|
||||
def testLeave(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
self.successResultOf(self.realm.createGroup(u"somechannel"))
|
||||
|
||||
user.write('JOIN #somechannel\r\n')
|
||||
user.transport.clear()
|
||||
|
||||
other = self._loggedInUser(u'otheruser')
|
||||
|
||||
other.write('JOIN #somechannel\r\n')
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
user.write('PART #somechannel\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
event = self._response(other)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], 'useruser!useruser@realmname')
|
||||
self.assertEqual(response[0][1], 'PART')
|
||||
self.assertEqual(response[0][2], ['#somechannel', 'leaving'])
|
||||
self.assertEqual(response, event)
|
||||
|
||||
# Now again, with a part message
|
||||
user.write('JOIN #somechannel\r\n')
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
user.write('PART #somechannel :goodbye stupidheads\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
event = self._response(other)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], 'useruser!useruser@realmname')
|
||||
self.assertEqual(response[0][1], 'PART')
|
||||
self.assertEqual(response[0][2], ['#somechannel', 'goodbye stupidheads'])
|
||||
self.assertEqual(response, event)
|
||||
|
||||
user.write(b'JOIN #somechannel\r\n')
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
user.write(b'PART #somechannel :goodbye stupidheads1\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
event = self._response(other)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], 'useruser!useruser@realmname')
|
||||
self.assertEqual(response[0][1], 'PART')
|
||||
self.assertEqual(response[0][2], ['#somechannel', 'goodbye stupidheads1'])
|
||||
self.assertEqual(response, event)
|
||||
|
||||
|
||||
def testGetTopic(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
group = service.Group("somechannel")
|
||||
group.meta["topic"] = "This is a test topic."
|
||||
group.meta["topic_author"] = "some_fellow"
|
||||
group.meta["topic_date"] = 77777777
|
||||
|
||||
self.successResultOf(self.realm.addGroup(group))
|
||||
|
||||
user.transport.clear()
|
||||
user.write("JOIN #somechannel\r\n")
|
||||
|
||||
response = self._response(user)
|
||||
|
||||
self.assertEqual(response[3][0], 'realmname')
|
||||
self.assertEqual(response[3][1], '332')
|
||||
|
||||
# XXX Sigh. irc.parsemsg() is not as correct as one might hope.
|
||||
self.assertEqual(response[3][2], ['useruser', '#somechannel', 'This is a test topic.'])
|
||||
self.assertEqual(response[4][1], '333')
|
||||
self.assertEqual(response[4][2], ['useruser', '#somechannel', 'some_fellow', '77777777'])
|
||||
|
||||
user.transport.clear()
|
||||
|
||||
user.write('TOPIC #somechannel\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
|
||||
self.assertEqual(response[0][1], '332')
|
||||
self.assertEqual(response[0][2], ['useruser', '#somechannel', 'This is a test topic.'])
|
||||
self.assertEqual(response[1][1], '333')
|
||||
self.assertEqual(response[1][2], ['useruser', '#somechannel', 'some_fellow', '77777777'])
|
||||
|
||||
|
||||
def testSetTopic(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
somechannel = self.successResultOf(
|
||||
self.realm.createGroup(u"somechannel"))
|
||||
|
||||
user.write("JOIN #somechannel\r\n")
|
||||
|
||||
other = self._loggedInUser(u'otheruser')
|
||||
|
||||
other.write("JOIN #somechannel\r\n")
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
other.write('TOPIC #somechannel :This is the new topic.\r\n')
|
||||
|
||||
response = self._response(other)
|
||||
event = self._response(user)
|
||||
|
||||
self.assertEqual(response, event)
|
||||
|
||||
self.assertEqual(response[0][0], 'otheruser!otheruser@realmname')
|
||||
self.assertEqual(response[0][1], 'TOPIC')
|
||||
self.assertEqual(response[0][2], ['#somechannel', 'This is the new topic.'])
|
||||
|
||||
other.transport.clear()
|
||||
|
||||
somechannel.meta['topic_date'] = 12345
|
||||
other.write('TOPIC #somechannel\r\n')
|
||||
|
||||
response = self._response(other)
|
||||
self.assertEqual(response[0][1], '332')
|
||||
self.assertEqual(response[0][2], ['otheruser', '#somechannel', 'This is the new topic.'])
|
||||
self.assertEqual(response[1][1], '333')
|
||||
self.assertEqual(response[1][2], ['otheruser', '#somechannel', 'otheruser', '12345'])
|
||||
|
||||
other.transport.clear()
|
||||
other.write('TOPIC #asdlkjasd\r\n')
|
||||
|
||||
response = self._response(other)
|
||||
self.assertEqual(response[0][1], '403')
|
||||
|
||||
|
||||
def testGroupMessage(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
self.successResultOf(self.realm.createGroup(u"somechannel"))
|
||||
|
||||
user.write("JOIN #somechannel\r\n")
|
||||
|
||||
other = self._loggedInUser(u'otheruser')
|
||||
|
||||
other.write("JOIN #somechannel\r\n")
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
user.write('PRIVMSG #somechannel :Hello, world.\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
event = self._response(other)
|
||||
|
||||
self.assertFalse(response)
|
||||
self.assertEqual(len(event), 1)
|
||||
self.assertEqual(event[0][0], 'useruser!useruser@realmname')
|
||||
self.assertEqual(event[0][1], 'PRIVMSG', -1)
|
||||
self.assertEqual(event[0][2], ['#somechannel', 'Hello, world.'])
|
||||
|
||||
|
||||
def testPrivateMessage(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
other = self._loggedInUser(u'otheruser')
|
||||
|
||||
user.transport.clear()
|
||||
other.transport.clear()
|
||||
|
||||
user.write('PRIVMSG otheruser :Hello, monkey.\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
event = self._response(other)
|
||||
|
||||
self.assertFalse(response)
|
||||
self.assertEqual(len(event), 1)
|
||||
self.assertEqual(event[0][0], 'useruser!useruser@realmname')
|
||||
self.assertEqual(event[0][1], 'PRIVMSG')
|
||||
self.assertEqual(event[0][2], ['otheruser', 'Hello, monkey.'])
|
||||
|
||||
user.write('PRIVMSG nousernamedthis :Hello, monkey.\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], 'realmname')
|
||||
self.assertEqual(response[0][1], '401')
|
||||
self.assertEqual(response[0][2], ['useruser', 'nousernamedthis', 'No such nick/channel.'])
|
||||
|
||||
|
||||
def testOper(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
user.transport.clear()
|
||||
user.write('OPER user pass\r\n')
|
||||
response = self._response(user)
|
||||
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][1], '491')
|
||||
|
||||
|
||||
def testGetUserMode(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
user.transport.clear()
|
||||
user.write('MODE useruser\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][0], 'realmname')
|
||||
self.assertEqual(response[0][1], '221')
|
||||
self.assertEqual(response[0][2], ['useruser', '+'])
|
||||
|
||||
|
||||
def testSetUserMode(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
user.transport.clear()
|
||||
user.write('MODE useruser +abcd\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][1], '472')
|
||||
|
||||
|
||||
def testGetGroupMode(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
self.successResultOf(self.realm.createGroup(u"somechannel"))
|
||||
|
||||
user.write('JOIN #somechannel\r\n')
|
||||
|
||||
user.transport.clear()
|
||||
user.write('MODE #somechannel\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][1], '324')
|
||||
|
||||
|
||||
def testSetGroupMode(self):
|
||||
user = self._loggedInUser(u'useruser')
|
||||
|
||||
self.successResultOf(self.realm.createGroup(u"groupname"))
|
||||
|
||||
user.write('JOIN #groupname\r\n')
|
||||
|
||||
user.transport.clear()
|
||||
user.write('MODE #groupname +abcd\r\n')
|
||||
|
||||
response = self._response(user)
|
||||
self.assertEqual(len(response), 1)
|
||||
self.assertEqual(response[0][1], '472')
|
||||
|
||||
|
||||
def testWho(self):
|
||||
group = service.Group('groupname')
|
||||
self.successResultOf(self.realm.addGroup(group))
|
||||
|
||||
users = []
|
||||
for nick in u'userone', u'usertwo', u'userthree':
|
||||
u = self._loggedInUser(nick)
|
||||
users.append(u)
|
||||
users[-1].write('JOIN #groupname\r\n')
|
||||
for user in users:
|
||||
user.transport.clear()
|
||||
|
||||
users[0].write('WHO #groupname\r\n')
|
||||
|
||||
r = self._response(users[0])
|
||||
self.assertFalse(self._response(users[1]))
|
||||
self.assertFalse(self._response(users[2]))
|
||||
|
||||
wantusers = ['userone', 'usertwo', 'userthree']
|
||||
for (prefix, code, stuff) in r[:-1]:
|
||||
self.assertEqual(prefix, 'realmname')
|
||||
self.assertEqual(code, '352')
|
||||
|
||||
(myname, group, theirname, theirhost, theirserver, theirnick, flag, extra) = stuff
|
||||
self.assertEqual(myname, 'userone')
|
||||
self.assertEqual(group, '#groupname')
|
||||
self.assertTrue(theirname in wantusers)
|
||||
self.assertEqual(theirhost, 'realmname')
|
||||
self.assertEqual(theirserver, 'realmname')
|
||||
wantusers.remove(theirnick)
|
||||
self.assertEqual(flag, 'H')
|
||||
self.assertEqual(extra, '0 ' + theirnick)
|
||||
self.assertFalse(wantusers)
|
||||
|
||||
prefix, code, stuff = r[-1]
|
||||
self.assertEqual(prefix, 'realmname')
|
||||
self.assertEqual(code, '315')
|
||||
myname, channel, extra = stuff
|
||||
self.assertEqual(myname, 'userone')
|
||||
self.assertEqual(channel, '#groupname')
|
||||
self.assertEqual(extra, 'End of /WHO list.')
|
||||
|
||||
|
||||
def testList(self):
|
||||
user = self._loggedInUser(u"someuser")
|
||||
user.transport.clear()
|
||||
|
||||
somegroup = self.successResultOf(self.realm.createGroup(u"somegroup"))
|
||||
somegroup.size = lambda: succeed(17)
|
||||
somegroup.meta['topic'] = 'this is the topic woo'
|
||||
|
||||
# Test one group
|
||||
user.write('LIST #somegroup\r\n')
|
||||
|
||||
r = self._response(user)
|
||||
self.assertEqual(len(r), 2)
|
||||
resp, end = r
|
||||
|
||||
self.assertEqual(resp[0], 'realmname')
|
||||
self.assertEqual(resp[1], '322')
|
||||
self.assertEqual(resp[2][0], 'someuser')
|
||||
self.assertEqual(resp[2][1], 'somegroup')
|
||||
self.assertEqual(resp[2][2], '17')
|
||||
self.assertEqual(resp[2][3], 'this is the topic woo')
|
||||
|
||||
self.assertEqual(end[0], 'realmname')
|
||||
self.assertEqual(end[1], '323')
|
||||
self.assertEqual(end[2][0], 'someuser')
|
||||
self.assertEqual(end[2][1], 'End of /LIST')
|
||||
|
||||
user.transport.clear()
|
||||
# Test all groups
|
||||
|
||||
user.write('LIST\r\n')
|
||||
r = self._response(user)
|
||||
self.assertEqual(len(r), 2)
|
||||
|
||||
fg1, end = r
|
||||
|
||||
self.assertEqual(fg1[1], '322')
|
||||
self.assertEqual(fg1[2][1], 'somegroup')
|
||||
self.assertEqual(fg1[2][2], '17')
|
||||
self.assertEqual(fg1[2][3], 'this is the topic woo')
|
||||
|
||||
self.assertEqual(end[1], '323')
|
||||
|
||||
|
||||
def testWhois(self):
|
||||
user = self._loggedInUser(u'someguy')
|
||||
|
||||
otherguy = service.User("otherguy")
|
||||
otherguy.itergroups = lambda: iter([
|
||||
service.Group('groupA'),
|
||||
service.Group('groupB')])
|
||||
otherguy.signOn = 10
|
||||
otherguy.lastMessage = time.time() - 15
|
||||
|
||||
self.successResultOf(self.realm.addUser(otherguy))
|
||||
|
||||
user.transport.clear()
|
||||
user.write('WHOIS otherguy\r\n')
|
||||
r = self._response(user)
|
||||
|
||||
self.assertEqual(len(r), 5)
|
||||
wuser, wserver, idle, channels, end = r
|
||||
|
||||
self.assertEqual(wuser[0], 'realmname')
|
||||
self.assertEqual(wuser[1], '311')
|
||||
self.assertEqual(wuser[2][0], 'someguy')
|
||||
self.assertEqual(wuser[2][1], 'otherguy')
|
||||
self.assertEqual(wuser[2][2], 'otherguy')
|
||||
self.assertEqual(wuser[2][3], 'realmname')
|
||||
self.assertEqual(wuser[2][4], '*')
|
||||
self.assertEqual(wuser[2][5], 'otherguy')
|
||||
|
||||
self.assertEqual(wserver[0], 'realmname')
|
||||
self.assertEqual(wserver[1], '312')
|
||||
self.assertEqual(wserver[2][0], 'someguy')
|
||||
self.assertEqual(wserver[2][1], 'otherguy')
|
||||
self.assertEqual(wserver[2][2], 'realmname')
|
||||
self.assertEqual(wserver[2][3], 'Hi mom!')
|
||||
|
||||
self.assertEqual(idle[0], 'realmname')
|
||||
self.assertEqual(idle[1], '317')
|
||||
self.assertEqual(idle[2][0], 'someguy')
|
||||
self.assertEqual(idle[2][1], 'otherguy')
|
||||
self.assertEqual(idle[2][2], '15')
|
||||
self.assertEqual(idle[2][3], '10')
|
||||
self.assertEqual(idle[2][4], "seconds idle, signon time")
|
||||
|
||||
self.assertEqual(channels[0], 'realmname')
|
||||
self.assertEqual(channels[1], '319')
|
||||
self.assertEqual(channels[2][0], 'someguy')
|
||||
self.assertEqual(channels[2][1], 'otherguy')
|
||||
self.assertEqual(channels[2][2], '#groupA #groupB')
|
||||
|
||||
self.assertEqual(end[0], 'realmname')
|
||||
self.assertEqual(end[1], '318')
|
||||
self.assertEqual(end[2][0], 'someguy')
|
||||
self.assertEqual(end[2][1], 'otherguy')
|
||||
self.assertEqual(end[2][2], 'End of WHOIS list.')
|
||||
|
||||
|
||||
class TestMind(service.PBMind):
|
||||
def __init__(self, *a, **kw):
|
||||
self.joins = []
|
||||
self.parts = []
|
||||
self.messages = []
|
||||
self.meta = []
|
||||
|
||||
def remote_userJoined(self, user, group):
|
||||
self.joins.append((user, group))
|
||||
|
||||
|
||||
def remote_userLeft(self, user, group, reason):
|
||||
self.parts.append((user, group, reason))
|
||||
|
||||
|
||||
def remote_receive(self, sender, recipient, message):
|
||||
self.messages.append((sender, recipient, message))
|
||||
|
||||
|
||||
def remote_groupMetaUpdate(self, group, meta):
|
||||
self.meta.append((group, meta))
|
||||
pb.setUnjellyableForClass(TestMind, service.PBMindReference)
|
||||
|
||||
|
||||
class PBProtocolTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.realm = service.InMemoryWordsRealm("realmname")
|
||||
self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
|
||||
self.portal = portal.Portal(
|
||||
self.realm, [self.checker])
|
||||
self.serverFactory = pb.PBServerFactory(self.portal)
|
||||
self.serverFactory.protocol = self._protocolFactory
|
||||
self.serverFactory.unsafeTracebacks = True
|
||||
self.clientFactory = pb.PBClientFactory()
|
||||
self.clientFactory.unsafeTracebacks = True
|
||||
self.serverPort = reactor.listenTCP(0, self.serverFactory)
|
||||
self.clientConn = reactor.connectTCP(
|
||||
'127.0.0.1',
|
||||
self.serverPort.getHost().port,
|
||||
self.clientFactory)
|
||||
|
||||
|
||||
def _protocolFactory(self, *args, **kw):
|
||||
self._serverProtocol = pb.Broker(0)
|
||||
return self._serverProtocol
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
d3 = Deferred()
|
||||
self._serverProtocol.notifyOnDisconnect(lambda: d3.callback(None))
|
||||
return DeferredList([
|
||||
maybeDeferred(self.serverPort.stopListening),
|
||||
maybeDeferred(self.clientConn.disconnect), d3])
|
||||
|
||||
|
||||
def _loggedInAvatar(self, name, password, mind):
|
||||
nameBytes = name
|
||||
if isinstance(name, unicode):
|
||||
nameBytes = name.encode("ascii")
|
||||
creds = credentials.UsernamePassword(nameBytes, password)
|
||||
self.checker.addUser(nameBytes, password)
|
||||
d = self.realm.createUser(name)
|
||||
d.addCallback(lambda ign: self.clientFactory.login(creds, mind))
|
||||
return d
|
||||
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def testGroups(self):
|
||||
mindone = TestMind()
|
||||
one = yield self._loggedInAvatar(u"one", b"p1", mindone)
|
||||
|
||||
mindtwo = TestMind()
|
||||
two = yield self._loggedInAvatar(u"two", b"p2", mindtwo)
|
||||
|
||||
mindThree = TestMind()
|
||||
three = yield self._loggedInAvatar(b"three", b"p3", mindThree)
|
||||
|
||||
yield self.realm.createGroup(u"foobar")
|
||||
yield self.realm.createGroup(b"barfoo")
|
||||
|
||||
groupone = yield one.join(u"foobar")
|
||||
grouptwo = yield two.join(b"barfoo")
|
||||
|
||||
yield two.join(u"foobar")
|
||||
yield two.join(b"barfoo")
|
||||
yield three.join(u"foobar")
|
||||
|
||||
yield groupone.send({b"text": b"hello, monkeys"})
|
||||
|
||||
yield groupone.leave()
|
||||
yield grouptwo.leave()
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from twisted.cred import credentials, error
|
||||
from twisted.words import tap
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
|
||||
class WordsTapTests(unittest.TestCase):
|
||||
"""
|
||||
Ensures that the twisted.words.tap API works.
|
||||
"""
|
||||
|
||||
PASSWD_TEXT = b"admin:admin\njoe:foo\n"
|
||||
admin = credentials.UsernamePassword(b'admin', b'admin')
|
||||
joeWrong = credentials.UsernamePassword(b'joe', b'bar')
|
||||
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create a file with two users.
|
||||
"""
|
||||
self.filename = self.mktemp()
|
||||
self.file = open(self.filename, 'wb')
|
||||
self.file.write(self.PASSWD_TEXT)
|
||||
self.file.flush()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Close the dummy user database.
|
||||
"""
|
||||
self.file.close()
|
||||
|
||||
|
||||
def test_hostname(self):
|
||||
"""
|
||||
Tests that the --hostname parameter gets passed to Options.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--hostname', 'myhost'])
|
||||
self.assertEqual(opt['hostname'], 'myhost')
|
||||
|
||||
|
||||
def test_passwd(self):
|
||||
"""
|
||||
Tests the --passwd command for backwards-compatibility.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--passwd', self.file.name])
|
||||
self._loginTest(opt)
|
||||
|
||||
|
||||
def test_auth(self):
|
||||
"""
|
||||
Tests that the --auth command generates a checker.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--auth', 'file:'+self.file.name])
|
||||
self._loginTest(opt)
|
||||
|
||||
|
||||
def _loginTest(self, opt):
|
||||
"""
|
||||
This method executes both positive and negative authentication
|
||||
tests against whatever credentials checker has been stored in
|
||||
the Options class.
|
||||
|
||||
@param opt: An instance of L{tap.Options}.
|
||||
"""
|
||||
self.assertEqual(len(opt['credCheckers']), 1)
|
||||
checker = opt['credCheckers'][0]
|
||||
self.assertFailure(checker.requestAvatarId(self.joeWrong),
|
||||
error.UnauthorizedLogin)
|
||||
def _gotAvatar(username):
|
||||
self.assertEqual(username, self.admin.username)
|
||||
return checker.requestAvatarId(self.admin).addCallback(_gotAvatar)
|
||||
|
|
@ -0,0 +1,348 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for twisted.words.xish.utility
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.xish import utility
|
||||
from twisted.words.xish.domish import Element
|
||||
from twisted.words.xish.utility import EventDispatcher
|
||||
|
||||
class CallbackTracker:
|
||||
"""
|
||||
Test helper for tracking callbacks.
|
||||
|
||||
Increases a counter on each call to L{call} and stores the object
|
||||
passed in the call.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.called = 0
|
||||
self.obj = None
|
||||
|
||||
|
||||
def call(self, obj):
|
||||
self.called = self.called + 1
|
||||
self.obj = obj
|
||||
|
||||
|
||||
|
||||
class OrderedCallbackTracker:
|
||||
"""
|
||||
Test helper for tracking callbacks and their order.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.callList = []
|
||||
|
||||
|
||||
def call1(self, object):
|
||||
self.callList.append(self.call1)
|
||||
|
||||
|
||||
def call2(self, object):
|
||||
self.callList.append(self.call2)
|
||||
|
||||
|
||||
def call3(self, object):
|
||||
self.callList.append(self.call3)
|
||||
|
||||
|
||||
|
||||
class EventDispatcherTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{EventDispatcher}.
|
||||
"""
|
||||
|
||||
def testStuff(self):
|
||||
d = EventDispatcher()
|
||||
cb1 = CallbackTracker()
|
||||
cb2 = CallbackTracker()
|
||||
cb3 = CallbackTracker()
|
||||
|
||||
d.addObserver("/message/body", cb1.call)
|
||||
d.addObserver("/message", cb1.call)
|
||||
d.addObserver("/presence", cb2.call)
|
||||
d.addObserver("//event/testevent", cb3.call)
|
||||
|
||||
msg = Element(("ns", "message"))
|
||||
msg.addElement("body")
|
||||
|
||||
pres = Element(("ns", "presence"))
|
||||
pres.addElement("presence")
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb1.called, 2)
|
||||
self.assertEqual(cb1.obj, msg)
|
||||
self.assertEqual(cb2.called, 0)
|
||||
|
||||
d.dispatch(pres)
|
||||
self.assertEqual(cb1.called, 2)
|
||||
self.assertEqual(cb2.called, 1)
|
||||
self.assertEqual(cb2.obj, pres)
|
||||
self.assertEqual(cb3.called, 0)
|
||||
|
||||
d.dispatch(d, "//event/testevent")
|
||||
self.assertEqual(cb3.called, 1)
|
||||
self.assertEqual(cb3.obj, d)
|
||||
|
||||
d.removeObserver("/presence", cb2.call)
|
||||
d.dispatch(pres)
|
||||
self.assertEqual(cb2.called, 1)
|
||||
|
||||
|
||||
def test_addObserverTwice(self):
|
||||
"""
|
||||
Test adding two observers for the same query.
|
||||
|
||||
When the event is dispatched both of the observers need to be called.
|
||||
"""
|
||||
d = EventDispatcher()
|
||||
cb1 = CallbackTracker()
|
||||
cb2 = CallbackTracker()
|
||||
|
||||
d.addObserver("//event/testevent", cb1.call)
|
||||
d.addObserver("//event/testevent", cb2.call)
|
||||
d.dispatch(d, "//event/testevent")
|
||||
|
||||
self.assertEqual(cb1.called, 1)
|
||||
self.assertEqual(cb1.obj, d)
|
||||
self.assertEqual(cb2.called, 1)
|
||||
self.assertEqual(cb2.obj, d)
|
||||
|
||||
|
||||
def test_addObserverInDispatch(self):
|
||||
"""
|
||||
Test for registration of an observer during dispatch.
|
||||
"""
|
||||
d = EventDispatcher()
|
||||
msg = Element(("ns", "message"))
|
||||
cb = CallbackTracker()
|
||||
|
||||
def onMessage(_):
|
||||
d.addObserver("/message", cb.call)
|
||||
|
||||
d.addOnetimeObserver("/message", onMessage)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 0)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 1)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 2)
|
||||
|
||||
|
||||
def test_addOnetimeObserverInDispatch(self):
|
||||
"""
|
||||
Test for registration of a onetime observer during dispatch.
|
||||
"""
|
||||
d = EventDispatcher()
|
||||
msg = Element(("ns", "message"))
|
||||
cb = CallbackTracker()
|
||||
|
||||
def onMessage(msg):
|
||||
d.addOnetimeObserver("/message", cb.call)
|
||||
|
||||
d.addOnetimeObserver("/message", onMessage)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 0)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 1)
|
||||
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 1)
|
||||
|
||||
|
||||
def testOnetimeDispatch(self):
|
||||
d = EventDispatcher()
|
||||
msg = Element(("ns", "message"))
|
||||
cb = CallbackTracker()
|
||||
|
||||
d.addOnetimeObserver("/message", cb.call)
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 1)
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.called, 1)
|
||||
|
||||
|
||||
def testDispatcherResult(self):
|
||||
d = EventDispatcher()
|
||||
msg = Element(("ns", "message"))
|
||||
pres = Element(("ns", "presence"))
|
||||
cb = CallbackTracker()
|
||||
|
||||
d.addObserver("/presence", cb.call)
|
||||
result = d.dispatch(msg)
|
||||
self.assertEqual(False, result)
|
||||
|
||||
result = d.dispatch(pres)
|
||||
self.assertEqual(True, result)
|
||||
|
||||
|
||||
def testOrderedXPathDispatch(self):
|
||||
d = EventDispatcher()
|
||||
cb = OrderedCallbackTracker()
|
||||
d.addObserver("/message/body", cb.call2)
|
||||
d.addObserver("/message", cb.call3, -1)
|
||||
d.addObserver("/message/body", cb.call1, 1)
|
||||
|
||||
msg = Element(("ns", "message"))
|
||||
msg.addElement("body")
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(cb.callList, [cb.call1, cb.call2, cb.call3],
|
||||
"Calls out of order: %s" %
|
||||
repr([c.__name__ for c in cb.callList]))
|
||||
|
||||
|
||||
# Observers are put into CallbackLists that are then put into dictionaries
|
||||
# keyed by the event trigger. Upon removal of the last observer for a
|
||||
# particular event trigger, the (now empty) CallbackList and corresponding
|
||||
# event trigger should be removed from those dictionaries to prevent
|
||||
# slowdown and memory leakage.
|
||||
|
||||
def test_cleanUpRemoveEventObserver(self):
|
||||
"""
|
||||
Test observer clean-up after removeObserver for named events.
|
||||
"""
|
||||
|
||||
d = EventDispatcher()
|
||||
cb = CallbackTracker()
|
||||
|
||||
d.addObserver('//event/test', cb.call)
|
||||
d.dispatch(None, '//event/test')
|
||||
self.assertEqual(1, cb.called)
|
||||
d.removeObserver('//event/test', cb.call)
|
||||
self.assertEqual(0, len(d._eventObservers.pop(0)))
|
||||
|
||||
|
||||
def test_cleanUpRemoveXPathObserver(self):
|
||||
"""
|
||||
Test observer clean-up after removeObserver for XPath events.
|
||||
"""
|
||||
|
||||
d = EventDispatcher()
|
||||
cb = CallbackTracker()
|
||||
msg = Element((None, "message"))
|
||||
|
||||
d.addObserver('/message', cb.call)
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(1, cb.called)
|
||||
d.removeObserver('/message', cb.call)
|
||||
self.assertEqual(0, len(d._xpathObservers.pop(0)))
|
||||
|
||||
|
||||
def test_cleanUpOnetimeEventObserver(self):
|
||||
"""
|
||||
Test observer clean-up after onetime named events.
|
||||
"""
|
||||
|
||||
d = EventDispatcher()
|
||||
cb = CallbackTracker()
|
||||
|
||||
d.addOnetimeObserver('//event/test', cb.call)
|
||||
d.dispatch(None, '//event/test')
|
||||
self.assertEqual(1, cb.called)
|
||||
self.assertEqual(0, len(d._eventObservers.pop(0)))
|
||||
|
||||
|
||||
def test_cleanUpOnetimeXPathObserver(self):
|
||||
"""
|
||||
Test observer clean-up after onetime XPath events.
|
||||
"""
|
||||
|
||||
d = EventDispatcher()
|
||||
cb = CallbackTracker()
|
||||
msg = Element((None, "message"))
|
||||
|
||||
d.addOnetimeObserver('/message', cb.call)
|
||||
d.dispatch(msg)
|
||||
self.assertEqual(1, cb.called)
|
||||
self.assertEqual(0, len(d._xpathObservers.pop(0)))
|
||||
|
||||
|
||||
def test_observerRaisingException(self):
|
||||
"""
|
||||
Test that exceptions in observers do not bubble up to dispatch.
|
||||
|
||||
The exceptions raised in observers should be logged and other
|
||||
observers should be called as if nothing happened.
|
||||
"""
|
||||
|
||||
class OrderedCallbackList(utility.CallbackList):
|
||||
def __init__(self):
|
||||
self.callbacks = OrderedDict()
|
||||
|
||||
class TestError(Exception):
|
||||
pass
|
||||
|
||||
def raiseError(_):
|
||||
raise TestError()
|
||||
|
||||
d = EventDispatcher()
|
||||
cb = CallbackTracker()
|
||||
|
||||
originalCallbackList = utility.CallbackList
|
||||
|
||||
try:
|
||||
utility.CallbackList = OrderedCallbackList
|
||||
|
||||
d.addObserver('//event/test', raiseError)
|
||||
d.addObserver('//event/test', cb.call)
|
||||
try:
|
||||
d.dispatch(None, '//event/test')
|
||||
except TestError:
|
||||
self.fail("TestError raised. Should have been logged instead.")
|
||||
|
||||
self.assertEqual(1, len(self.flushLoggedErrors(TestError)))
|
||||
self.assertEqual(1, cb.called)
|
||||
finally:
|
||||
utility.CallbackList = originalCallbackList
|
||||
|
||||
|
||||
|
||||
class XmlPipeTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{twisted.words.xish.utility.XmlPipe}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.pipe = utility.XmlPipe()
|
||||
|
||||
|
||||
def test_sendFromSource(self):
|
||||
"""
|
||||
Send an element from the source and observe it from the sink.
|
||||
"""
|
||||
def cb(obj):
|
||||
called.append(obj)
|
||||
|
||||
called = []
|
||||
self.pipe.sink.addObserver('/test[@xmlns="testns"]', cb)
|
||||
element = Element(('testns', 'test'))
|
||||
self.pipe.source.send(element)
|
||||
self.assertEqual([element], called)
|
||||
|
||||
|
||||
def test_sendFromSink(self):
|
||||
"""
|
||||
Send an element from the sink and observe it from the source.
|
||||
"""
|
||||
def cb(obj):
|
||||
called.append(obj)
|
||||
|
||||
called = []
|
||||
self.pipe.source.addObserver('/test[@xmlns="testns"]', cb)
|
||||
element = Element(('testns', 'test'))
|
||||
self.pipe.sink.send(element)
|
||||
self.assertEqual([element], called)
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.xish.xmlstream}.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.internet import protocol
|
||||
from twisted.python import failure
|
||||
from twisted.trial import unittest
|
||||
from twisted.words.xish import domish, utility, xmlstream
|
||||
|
||||
class XmlStreamTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.connectionLostMsg = "no reason"
|
||||
self.outlist = []
|
||||
self.xmlstream = xmlstream.XmlStream()
|
||||
self.xmlstream.transport = self
|
||||
self.xmlstream.transport.write = self.outlist.append
|
||||
|
||||
|
||||
def loseConnection(self):
|
||||
"""
|
||||
Stub loseConnection because we are a transport.
|
||||
"""
|
||||
self.xmlstream.connectionLost(failure.Failure(
|
||||
Exception(self.connectionLostMsg)))
|
||||
|
||||
|
||||
def test_send(self):
|
||||
"""
|
||||
Calling L{xmlstream.XmlStream.send} results in the data being written
|
||||
to the transport.
|
||||
"""
|
||||
self.xmlstream.connectionMade()
|
||||
self.xmlstream.send(b"<root>")
|
||||
self.assertEqual(self.outlist[0], b"<root>")
|
||||
|
||||
|
||||
def test_receiveRoot(self):
|
||||
"""
|
||||
Receiving the starttag of the root element results in stream start.
|
||||
"""
|
||||
streamStarted = []
|
||||
|
||||
def streamStartEvent(rootelem):
|
||||
streamStarted.append(None)
|
||||
|
||||
self.xmlstream.addObserver(xmlstream.STREAM_START_EVENT,
|
||||
streamStartEvent)
|
||||
self.xmlstream.connectionMade()
|
||||
self.xmlstream.dataReceived("<root>")
|
||||
self.assertEqual(1, len(streamStarted))
|
||||
|
||||
|
||||
def test_receiveBadXML(self):
|
||||
"""
|
||||
Receiving malformed XML results in an L{STREAM_ERROR_EVENT}.
|
||||
"""
|
||||
streamError = []
|
||||
streamEnd = []
|
||||
|
||||
def streamErrorEvent(reason):
|
||||
streamError.append(reason)
|
||||
|
||||
def streamEndEvent(_):
|
||||
streamEnd.append(None)
|
||||
|
||||
self.xmlstream.addObserver(xmlstream.STREAM_ERROR_EVENT,
|
||||
streamErrorEvent)
|
||||
self.xmlstream.addObserver(xmlstream.STREAM_END_EVENT,
|
||||
streamEndEvent)
|
||||
self.xmlstream.connectionMade()
|
||||
|
||||
self.xmlstream.dataReceived("<root>")
|
||||
self.assertEqual(0, len(streamError))
|
||||
self.assertEqual(0, len(streamEnd))
|
||||
|
||||
self.xmlstream.dataReceived("<child><unclosed></child>")
|
||||
self.assertEqual(1, len(streamError))
|
||||
self.assertTrue(streamError[0].check(domish.ParserError))
|
||||
self.assertEqual(1, len(streamEnd))
|
||||
|
||||
|
||||
def test_streamEnd(self):
|
||||
"""
|
||||
Ending the stream fires a L{STREAM_END_EVENT}.
|
||||
"""
|
||||
streamEnd = []
|
||||
|
||||
def streamEndEvent(reason):
|
||||
streamEnd.append(reason)
|
||||
|
||||
self.xmlstream.addObserver(xmlstream.STREAM_END_EVENT,
|
||||
streamEndEvent)
|
||||
self.xmlstream.connectionMade()
|
||||
self.loseConnection()
|
||||
self.assertEqual(1, len(streamEnd))
|
||||
self.assertIsInstance(streamEnd[0], failure.Failure)
|
||||
self.assertEqual(streamEnd[0].getErrorMessage(),
|
||||
self.connectionLostMsg)
|
||||
|
||||
|
||||
|
||||
class DummyProtocol(protocol.Protocol, utility.EventDispatcher):
|
||||
"""
|
||||
I am a protocol with an event dispatcher without further processing.
|
||||
|
||||
This protocol is only used for testing XmlStreamFactoryMixin to make
|
||||
sure the bootstrap observers are added to the protocol instance.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
self.observers = []
|
||||
|
||||
utility.EventDispatcher.__init__(self)
|
||||
|
||||
|
||||
|
||||
class BootstrapMixinTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{xmlstream.BootstrapMixin}.
|
||||
|
||||
@ivar factory: Instance of the factory or mixin under test.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = xmlstream.BootstrapMixin()
|
||||
|
||||
|
||||
def test_installBootstraps(self):
|
||||
"""
|
||||
Dispatching an event fires registered bootstrap observers.
|
||||
"""
|
||||
called = []
|
||||
|
||||
def cb(data):
|
||||
called.append(data)
|
||||
|
||||
dispatcher = DummyProtocol()
|
||||
self.factory.addBootstrap('//event/myevent', cb)
|
||||
self.factory.installBootstraps(dispatcher)
|
||||
|
||||
dispatcher.dispatch(None, '//event/myevent')
|
||||
self.assertEqual(1, len(called))
|
||||
|
||||
|
||||
def test_addAndRemoveBootstrap(self):
|
||||
"""
|
||||
Test addition and removal of a bootstrap event handler.
|
||||
"""
|
||||
|
||||
called = []
|
||||
|
||||
def cb(data):
|
||||
called.append(data)
|
||||
|
||||
self.factory.addBootstrap('//event/myevent', cb)
|
||||
self.factory.removeBootstrap('//event/myevent', cb)
|
||||
|
||||
dispatcher = DummyProtocol()
|
||||
self.factory.installBootstraps(dispatcher)
|
||||
|
||||
dispatcher.dispatch(None, '//event/myevent')
|
||||
self.assertFalse(called)
|
||||
|
||||
|
||||
|
||||
class GenericXmlStreamFactoryTestsMixin(BootstrapMixinTests):
|
||||
"""
|
||||
Generic tests for L{XmlStream} factories.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = xmlstream.XmlStreamFactory()
|
||||
|
||||
|
||||
def test_buildProtocolInstallsBootstraps(self):
|
||||
"""
|
||||
The protocol factory installs bootstrap event handlers on the protocol.
|
||||
"""
|
||||
called = []
|
||||
|
||||
def cb(data):
|
||||
called.append(data)
|
||||
|
||||
self.factory.addBootstrap('//event/myevent', cb)
|
||||
|
||||
xs = self.factory.buildProtocol(None)
|
||||
xs.dispatch(None, '//event/myevent')
|
||||
|
||||
self.assertEqual(1, len(called))
|
||||
|
||||
|
||||
def test_buildProtocolStoresFactory(self):
|
||||
"""
|
||||
The protocol factory is saved in the protocol.
|
||||
"""
|
||||
xs = self.factory.buildProtocol(None)
|
||||
self.assertIdentical(self.factory, xs.factory)
|
||||
|
||||
|
||||
|
||||
class XmlStreamFactoryMixinTests(GenericXmlStreamFactoryTestsMixin):
|
||||
"""
|
||||
Tests for L{xmlstream.XmlStreamFactoryMixin}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.factory = xmlstream.XmlStreamFactoryMixin(None, test=None)
|
||||
self.factory.protocol = DummyProtocol
|
||||
|
||||
|
||||
def test_buildProtocolFactoryArguments(self):
|
||||
"""
|
||||
Arguments passed to the factory are passed to protocol on
|
||||
instantiation.
|
||||
"""
|
||||
xs = self.factory.buildProtocol(None)
|
||||
|
||||
self.assertEqual((None,), xs.args)
|
||||
self.assertEqual({'test': None}, xs.kwargs)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.words.xmpproutertap}.
|
||||
"""
|
||||
|
||||
from twisted.application import internet
|
||||
from twisted.trial import unittest
|
||||
from twisted.words import xmpproutertap as tap
|
||||
from twisted.words.protocols.jabber import component
|
||||
|
||||
class XMPPRouterTapTests(unittest.TestCase):
|
||||
|
||||
def test_port(self):
|
||||
"""
|
||||
The port option is recognised as a parameter.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--port', '7001'])
|
||||
self.assertEqual(opt['port'], '7001')
|
||||
|
||||
|
||||
def test_portDefault(self):
|
||||
"""
|
||||
The port option has '5347' as default value
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions([])
|
||||
self.assertEqual(opt['port'], 'tcp:5347:interface=127.0.0.1')
|
||||
|
||||
|
||||
def test_secret(self):
|
||||
"""
|
||||
The secret option is recognised as a parameter.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--secret', 'hushhush'])
|
||||
self.assertEqual(opt['secret'], 'hushhush')
|
||||
|
||||
|
||||
def test_secretDefault(self):
|
||||
"""
|
||||
The secret option has 'secret' as default value
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions([])
|
||||
self.assertEqual(opt['secret'], 'secret')
|
||||
|
||||
|
||||
def test_verbose(self):
|
||||
"""
|
||||
The verbose option is recognised as a flag.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--verbose'])
|
||||
self.assertTrue(opt['verbose'])
|
||||
|
||||
|
||||
def test_makeService(self):
|
||||
"""
|
||||
The service gets set up with a router and factory.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions([])
|
||||
s = tap.makeService(opt)
|
||||
self.assertIsInstance(s, internet.StreamServerEndpointService)
|
||||
self.assertEqual('127.0.0.1', s.endpoint._interface)
|
||||
self.assertEqual(5347, s.endpoint._port)
|
||||
factory = s.factory
|
||||
self.assertIsInstance(factory, component.XMPPComponentServerFactory)
|
||||
self.assertIsInstance(factory.router, component.Router)
|
||||
self.assertEqual('secret', factory.secret)
|
||||
self.assertFalse(factory.logTraffic)
|
||||
|
||||
|
||||
def test_makeServiceVerbose(self):
|
||||
"""
|
||||
The verbose flag enables traffic logging.
|
||||
"""
|
||||
opt = tap.Options()
|
||||
opt.parseOptions(['--verbose'])
|
||||
s = tap.makeService(opt)
|
||||
self.assertTrue(s.factory.logTraffic)
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
from __future__ import absolute_import, division
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
from twisted.words.xish import xpath
|
||||
from twisted.words.xish.domish import Element
|
||||
from twisted.words.xish.xpath import XPathQuery
|
||||
from twisted.words.xish.xpathparser import SyntaxError
|
||||
|
||||
class XPathTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Build element:
|
||||
# <foo xmlns='testns' attrib1='value1' attrib3="user@host/resource">
|
||||
# somecontent
|
||||
# <bar>
|
||||
# <foo>
|
||||
# <gar>DEF</gar>
|
||||
# </foo>
|
||||
# </bar>
|
||||
# somemorecontent
|
||||
# <bar attrib2="value2">
|
||||
# <bar>
|
||||
# <foo/>
|
||||
# <gar>ABC</gar>
|
||||
# </bar>
|
||||
# <bar/>
|
||||
# <bar attrib4='value4' attrib5='value5'>
|
||||
# <foo/>
|
||||
# <gar>JKL</gar>
|
||||
# </bar>
|
||||
# <bar attrib4='value4' attrib5='value4'>
|
||||
# <foo/>
|
||||
# <gar>MNO</gar>
|
||||
# </bar>
|
||||
# <bar attrib4='value4' attrib5='value6' attrib6='á'>
|
||||
# <quux>☃</quux>
|
||||
# </bar>
|
||||
# </foo>
|
||||
self.e = Element(("testns", "foo"))
|
||||
self.e["attrib1"] = "value1"
|
||||
self.e["attrib3"] = "user@host/resource"
|
||||
self.e.addContent(u"somecontent")
|
||||
self.bar1 = self.e.addElement("bar")
|
||||
self.subfoo = self.bar1.addElement("foo")
|
||||
self.gar1 = self.subfoo.addElement("gar")
|
||||
self.gar1.addContent(u"DEF")
|
||||
self.e.addContent(u"somemorecontent")
|
||||
self.bar2 = self.e.addElement("bar")
|
||||
self.bar2["attrib2"] = "value2"
|
||||
self.bar3 = self.bar2.addElement("bar")
|
||||
self.subfoo2 = self.bar3.addElement("foo")
|
||||
self.gar2 = self.bar3.addElement("gar")
|
||||
self.gar2.addContent(u"ABC")
|
||||
self.bar4 = self.e.addElement("bar")
|
||||
self.bar5 = self.e.addElement("bar")
|
||||
self.bar5["attrib4"] = "value4"
|
||||
self.bar5["attrib5"] = "value5"
|
||||
self.subfoo3 = self.bar5.addElement("foo")
|
||||
self.gar3 = self.bar5.addElement("gar")
|
||||
self.gar3.addContent(u"JKL")
|
||||
self.bar6 = self.e.addElement("bar")
|
||||
self.bar6["attrib4"] = "value4"
|
||||
self.bar6["attrib5"] = "value4"
|
||||
self.subfoo4 = self.bar6.addElement("foo")
|
||||
self.gar4 = self.bar6.addElement("gar")
|
||||
self.gar4.addContent(u"MNO")
|
||||
self.bar7 = self.e.addElement("bar")
|
||||
self.bar7["attrib4"] = "value4"
|
||||
self.bar7["attrib5"] = "value6"
|
||||
self.bar7["attrib6"] = u"á"
|
||||
self.quux = self.bar7.addElement("quux")
|
||||
self.quux.addContent(u"\N{SNOWMAN}")
|
||||
|
||||
def test_staticMethods(self):
|
||||
"""
|
||||
Test basic operation of the static methods.
|
||||
"""
|
||||
self.assertEqual(xpath.matches("/foo/bar", self.e),
|
||||
True)
|
||||
self.assertEqual(xpath.queryForNodes("/foo/bar", self.e),
|
||||
[self.bar1, self.bar2, self.bar4,
|
||||
self.bar5, self.bar6, self.bar7])
|
||||
self.assertEqual(xpath.queryForString("/foo", self.e),
|
||||
"somecontent")
|
||||
self.assertEqual(xpath.queryForStringList("/foo", self.e),
|
||||
["somecontent", "somemorecontent"])
|
||||
|
||||
def test_locationFooBar(self):
|
||||
"""
|
||||
Test matching foo with child bar.
|
||||
"""
|
||||
xp = XPathQuery("/foo/bar")
|
||||
self.assertEqual(xp.matches(self.e), 1)
|
||||
|
||||
def test_locationFooBarFoo(self):
|
||||
"""
|
||||
Test finding foos at the second level.
|
||||
"""
|
||||
xp = XPathQuery("/foo/bar/foo")
|
||||
self.assertEqual(xp.matches(self.e), 1)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.subfoo,
|
||||
self.subfoo3,
|
||||
self.subfoo4])
|
||||
|
||||
def test_locationNoBar3(self):
|
||||
"""
|
||||
Test not finding bar3.
|
||||
"""
|
||||
xp = XPathQuery("/foo/bar3")
|
||||
self.assertEqual(xp.matches(self.e), 0)
|
||||
|
||||
def test_locationAllChilds(self):
|
||||
"""
|
||||
Test finding childs of foo.
|
||||
"""
|
||||
xp = XPathQuery("/foo/*")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
|
||||
self.bar4, self.bar5,
|
||||
self.bar6, self.bar7])
|
||||
|
||||
def test_attribute(self):
|
||||
"""
|
||||
Test matching foo with attribute.
|
||||
"""
|
||||
xp = XPathQuery("/foo[@attrib1]")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
|
||||
def test_attributeWithValueAny(self):
|
||||
"""
|
||||
Test find nodes with attribute having value.
|
||||
"""
|
||||
xp = XPathQuery("/foo/*[@attrib2='value2']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar2])
|
||||
|
||||
def test_locationWithValueUnicode(self):
|
||||
"""
|
||||
Nodes' attributes can be matched with non-ASCII values.
|
||||
"""
|
||||
xp = XPathQuery(u"/foo/*[@attrib6='á']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar7])
|
||||
|
||||
def test_namespaceFound(self):
|
||||
"""
|
||||
Test matching node with namespace.
|
||||
"""
|
||||
xp = XPathQuery("/foo[@xmlns='testns']/bar")
|
||||
self.assertEqual(xp.matches(self.e), 1)
|
||||
|
||||
def test_namespaceNotFound(self):
|
||||
"""
|
||||
Test not matching node with wrong namespace.
|
||||
"""
|
||||
xp = XPathQuery("/foo[@xmlns='badns']/bar2")
|
||||
self.assertEqual(xp.matches(self.e), 0)
|
||||
|
||||
def test_attributeWithValue(self):
|
||||
"""
|
||||
Test matching node with attribute having value.
|
||||
"""
|
||||
xp = XPathQuery("/foo[@attrib1='value1']")
|
||||
self.assertEqual(xp.matches(self.e), 1)
|
||||
|
||||
def test_queryForString(self):
|
||||
"""
|
||||
queryforString on absolute paths returns their first CDATA.
|
||||
"""
|
||||
xp = XPathQuery("/foo")
|
||||
self.assertEqual(xp.queryForString(self.e), "somecontent")
|
||||
|
||||
def test_queryForStringList(self):
|
||||
"""
|
||||
queryforStringList on absolute paths returns all their CDATA.
|
||||
"""
|
||||
xp = XPathQuery("/foo")
|
||||
self.assertEqual(xp.queryForStringList(self.e),
|
||||
["somecontent", "somemorecontent"])
|
||||
|
||||
def test_queryForStringListAnyLocation(self):
|
||||
"""
|
||||
queryforStringList on relative paths returns all their CDATA.
|
||||
"""
|
||||
xp = XPathQuery("//foo")
|
||||
self.assertEqual(xp.queryForStringList(self.e),
|
||||
["somecontent", "somemorecontent"])
|
||||
|
||||
def test_queryForNodes(self):
|
||||
"""
|
||||
Test finding nodes.
|
||||
"""
|
||||
xp = XPathQuery("/foo/bar")
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
|
||||
self.bar4, self.bar5,
|
||||
self.bar6, self.bar7])
|
||||
|
||||
def test_textCondition(self):
|
||||
"""
|
||||
Test matching a node with given text.
|
||||
"""
|
||||
xp = XPathQuery("/foo[text() = 'somecontent']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
|
||||
def test_textConditionUnicode(self):
|
||||
"""
|
||||
A node can be matched by text with non-ascii code points.
|
||||
"""
|
||||
xp = XPathQuery(u"//*[text()='\N{SNOWMAN}']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.quux])
|
||||
|
||||
def test_textNotOperator(self):
|
||||
"""
|
||||
Test for not operator.
|
||||
"""
|
||||
xp = XPathQuery("/foo[not(@nosuchattrib)]")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
|
||||
def test_anyLocationAndText(self):
|
||||
"""
|
||||
Test finding any nodes named gar and getting their text contents.
|
||||
"""
|
||||
xp = XPathQuery("//gar")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.gar1, self.gar2,
|
||||
self.gar3, self.gar4])
|
||||
self.assertEqual(xp.queryForStringList(self.e), ["DEF", "ABC",
|
||||
"JKL", "MNO"])
|
||||
|
||||
def test_anyLocation(self):
|
||||
"""
|
||||
Test finding any nodes named bar.
|
||||
"""
|
||||
xp = XPathQuery("//bar")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar1, self.bar2,
|
||||
self.bar3, self.bar4,
|
||||
self.bar5, self.bar6,
|
||||
self.bar7])
|
||||
|
||||
def test_anyLocationQueryForString(self):
|
||||
"""
|
||||
L{XPathQuery.queryForString} should raise a L{NotImplementedError}
|
||||
for any location.
|
||||
"""
|
||||
xp = XPathQuery("//bar")
|
||||
self.assertRaises(NotImplementedError, xp.queryForString, None)
|
||||
|
||||
def test_andOperator(self):
|
||||
"""
|
||||
Test boolean and operator in condition.
|
||||
"""
|
||||
xp = XPathQuery("//bar[@attrib4='value4' and @attrib5='value5']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar5])
|
||||
|
||||
def test_orOperator(self):
|
||||
"""
|
||||
Test boolean or operator in condition.
|
||||
"""
|
||||
xp = XPathQuery("//bar[@attrib5='value4' or @attrib5='value5']")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar5, self.bar6])
|
||||
|
||||
def test_booleanOperatorsParens(self):
|
||||
"""
|
||||
Test multiple boolean operators in condition with parens.
|
||||
"""
|
||||
xp = XPathQuery("""//bar[@attrib4='value4' and
|
||||
(@attrib5='value4' or @attrib5='value6')]""")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar6, self.bar7])
|
||||
|
||||
def test_booleanOperatorsNoParens(self):
|
||||
"""
|
||||
Test multiple boolean operators in condition without parens.
|
||||
"""
|
||||
xp = XPathQuery("""//bar[@attrib5='value4' or
|
||||
@attrib5='value5' or
|
||||
@attrib5='value6']""")
|
||||
self.assertEqual(xp.matches(self.e), True)
|
||||
self.assertEqual(xp.queryForNodes(self.e), [self.bar5, self.bar6, self.bar7])
|
||||
|
||||
def test_badXPathNoClosingBracket(self):
|
||||
"""
|
||||
A missing closing bracket raises a SyntaxError.
|
||||
|
||||
This test excercises the most common failure mode.
|
||||
"""
|
||||
exc = self.assertRaises(SyntaxError, XPathQuery, """//bar[@attrib1""")
|
||||
self.assertTrue(exc.msg.startswith("Trying to find one of"),
|
||||
("SyntaxError message '%s' doesn't start with "
|
||||
"'Trying to find one of'") % exc.msg)
|
||||
Loading…
Add table
Add a link
Reference in a new issue