diff options
Diffstat (limited to 'sleekxmpp/test/sleektest.py')
-rw-r--r-- | sleekxmpp/test/sleektest.py | 772 |
1 files changed, 0 insertions, 772 deletions
diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py deleted file mode 100644 index d28f77e2..00000000 --- a/sleekxmpp/test/sleektest.py +++ /dev/null @@ -1,772 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permission. -""" - -import unittest -from xml.parsers.expat import ExpatError - -from sleekxmpp import ClientXMPP, ComponentXMPP -from sleekxmpp.util import Queue -from sleekxmpp.stanza import Message, Iq, Presence -from sleekxmpp.test import TestSocket, TestLiveSocket -from sleekxmpp.xmlstream import ET -from sleekxmpp.xmlstream import ElementBase -from sleekxmpp.xmlstream.tostring import tostring -from sleekxmpp.xmlstream.matcher import StanzaPath, MatcherId, MatchIDSender -from sleekxmpp.xmlstream.matcher import MatchXMLMask, MatchXPath - - -class SleekTest(unittest.TestCase): - - """ - A SleekXMPP specific TestCase class that provides - methods for comparing message, iq, and presence stanzas. - - Methods: - Message -- Create a Message stanza object. - Iq -- Create an Iq stanza object. - Presence -- Create a Presence stanza object. - check_jid -- Check a JID and its component parts. - check -- Compare a stanza against an XML string. - stream_start -- Initialize a dummy XMPP client. - stream_close -- Disconnect the XMPP client. - make_header -- Create a stream header. - send_header -- Check that the given header has been sent. - send_feature -- Send a raw XML element. - send -- Check that the XMPP client sent the given - generic stanza. - recv -- Queue data for XMPP client to receive, or - verify the data that was received from a - live connection. - recv_header -- Check that a given stream header - was received. - recv_feature -- Check that a given, raw XML element - was recveived. - fix_namespaces -- Add top-level namespace to an XML object. - compare -- Compare XML objects against each other. - """ - - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - self.xmpp = None - - def parse_xml(self, xml_string): - try: - xml = ET.fromstring(xml_string) - return xml - except (SyntaxError, ExpatError) as e: - msg = e.msg if hasattr(e, 'msg') else e.message - if 'unbound' in msg: - known_prefixes = { - 'stream': 'http://etherx.jabber.org/streams'} - - prefix = xml_string.split('<')[1].split(':')[0] - if prefix in known_prefixes: - xml_string = '<fixns xmlns:%s="%s">%s</fixns>' % ( - prefix, - known_prefixes[prefix], - xml_string) - xml = self.parse_xml(xml_string) - xml = list(xml)[0] - return xml - else: - self.fail("XML data was mal-formed:\n%s" % xml_string) - - # ------------------------------------------------------------------ - # Shortcut methods for creating stanza objects - - def Message(self, *args, **kwargs): - """ - Create a Message stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Message's values. - """ - return Message(self.xmpp, *args, **kwargs) - - def Iq(self, *args, **kwargs): - """ - Create an Iq stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Iq's values. - """ - return Iq(self.xmpp, *args, **kwargs) - - def Presence(self, *args, **kwargs): - """ - Create a Presence stanza. - - Uses same arguments as StanzaBase.__init__ - - Arguments: - xml -- An XML object to use for the Iq's values. - """ - return Presence(self.xmpp, *args, **kwargs) - - def check_jid(self, jid, user=None, domain=None, resource=None, - bare=None, full=None, string=None): - """ - Verify the components of a JID. - - Arguments: - jid -- The JID object to test. - user -- Optional. The user name portion of the JID. - domain -- Optional. The domain name portion of the JID. - resource -- Optional. The resource portion of the JID. - bare -- Optional. The bare JID. - full -- Optional. The full JID. - string -- Optional. The string version of the JID. - """ - if user is not None: - self.assertEqual(jid.user, user, - "User does not match: %s" % jid.user) - if domain is not None: - self.assertEqual(jid.domain, domain, - "Domain does not match: %s" % jid.domain) - if resource is not None: - self.assertEqual(jid.resource, resource, - "Resource does not match: %s" % jid.resource) - if bare is not None: - self.assertEqual(jid.bare, bare, - "Bare JID does not match: %s" % jid.bare) - if full is not None: - self.assertEqual(jid.full, full, - "Full JID does not match: %s" % jid.full) - if string is not None: - self.assertEqual(str(jid), string, - "String does not match: %s" % str(jid)) - - def check_roster(self, owner, jid, name=None, subscription=None, - afrom=None, ato=None, pending_out=None, pending_in=None, - groups=None): - roster = self.xmpp.roster[owner][jid] - if name is not None: - self.assertEqual(roster['name'], name, - "Incorrect name value: %s" % roster['name']) - if subscription is not None: - self.assertEqual(roster['subscription'], subscription, - "Incorrect subscription: %s" % roster['subscription']) - if afrom is not None: - self.assertEqual(roster['from'], afrom, - "Incorrect from state: %s" % roster['from']) - if ato is not None: - self.assertEqual(roster['to'], ato, - "Incorrect to state: %s" % roster['to']) - if pending_out is not None: - self.assertEqual(roster['pending_out'], pending_out, - "Incorrect pending_out state: %s" % roster['pending_out']) - if pending_in is not None: - self.assertEqual(roster['pending_in'], pending_out, - "Incorrect pending_in state: %s" % roster['pending_in']) - if groups is not None: - self.assertEqual(roster['groups'], groups, - "Incorrect groups: %s" % roster['groups']) - - # ------------------------------------------------------------------ - # Methods for comparing stanza objects to XML strings - - def check(self, stanza, criteria, method='exact', - defaults=None, use_values=True): - """ - Create and compare several stanza objects to a correct XML string. - - If use_values is False, tests using stanza.values will not be used. - - Some stanzas provide default values for some interfaces, but - these defaults can be problematic for testing since they can easily - be forgotten when supplying the XML string. A list of interfaces that - use defaults may be provided and the generated stanzas will use the - default values for those interfaces if needed. - - However, correcting the supplied XML is not possible for interfaces - that add or remove XML elements. Only interfaces that map to XML - attributes may be set using the defaults parameter. The supplied XML - must take into account any extra elements that are included by default. - - Arguments: - stanza -- The stanza object to test. - criteria -- An expression the stanza must match against. - method -- The type of matching to use; one of: - 'exact', 'mask', 'id', 'xpath', and 'stanzapath'. - Defaults to the value of self.match_method. - defaults -- A list of stanza interfaces that have default - values. These interfaces will be set to their - defaults for the given and generated stanzas to - prevent unexpected test failures. - use_values -- Indicates if testing using stanza.values should - be used. Defaults to True. - """ - if method is None and hasattr(self, 'match_method'): - method = getattr(self, 'match_method') - - if method != 'exact': - matchers = {'stanzapath': StanzaPath, - 'xpath': MatchXPath, - 'mask': MatchXMLMask, - 'idsender': MatchIDSender, - 'id': MatcherId} - Matcher = matchers.get(method, None) - if Matcher is None: - raise ValueError("Unknown matching method.") - test = Matcher(criteria) - self.failUnless(test.match(stanza), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % str(criteria) + \ - "Stanza:\n%s" % str(stanza)) - else: - stanza_class = stanza.__class__ - if not isinstance(criteria, ElementBase): - xml = self.parse_xml(criteria) - else: - xml = criteria.xml - - # Ensure that top level namespaces are used, even if they - # were not provided. - self.fix_namespaces(stanza.xml, 'jabber:client') - self.fix_namespaces(xml, 'jabber:client') - - stanza2 = stanza_class(xml=xml) - - if use_values: - # Using stanza.values will add XML for any interface that - # has a default value. We need to set those defaults on - # the existing stanzas and XML so that they will compare - # correctly. - default_stanza = stanza_class() - if defaults is None: - known_defaults = { - Message: ['type'], - Presence: ['priority'] - } - defaults = known_defaults.get(stanza_class, []) - for interface in defaults: - stanza[interface] = stanza[interface] - stanza2[interface] = stanza2[interface] - # Can really only automatically add defaults for top - # level attribute values. Anything else must be accounted - # for in the provided XML string. - if interface not in xml.attrib: - if interface in default_stanza.xml.attrib: - value = default_stanza.xml.attrib[interface] - xml.attrib[interface] = value - - values = stanza2.values - stanza3 = stanza_class() - stanza3.values = values - - debug = "Three methods for creating stanzas do not match.\n" - debug += "Given XML:\n%s\n" % tostring(xml) - debug += "Given stanza:\n%s\n" % tostring(stanza.xml) - debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml) - debug += "Second generated stanza:\n%s\n" % tostring(stanza3.xml) - result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml) - else: - debug = "Two methods for creating stanzas do not match.\n" - debug += "Given XML:\n%s\n" % tostring(xml) - debug += "Given stanza:\n%s\n" % tostring(stanza.xml) - debug += "Generated stanza:\n%s\n" % tostring(stanza2.xml) - result = self.compare(xml, stanza.xml, stanza2.xml) - - self.failUnless(result, debug) - - # ------------------------------------------------------------------ - # Methods for simulating stanza streams. - - def stream_disconnect(self): - """ - Simulate a stream disconnection. - """ - if self.xmpp: - self.xmpp.socket.disconnect_error() - - def stream_start(self, mode='client', skip=True, header=None, - socket='mock', jid='tester@localhost', - password='test', server='localhost', - port=5222, sasl_mech=None, - plugins=None, plugin_config={}): - """ - Initialize an XMPP client or component using a dummy XML stream. - - Arguments: - mode -- Either 'client' or 'component'. Defaults to 'client'. - skip -- Indicates if the first item in the sent queue (the - stream header) should be removed. Tests that wish - to test initializing the stream should set this to - False. Otherwise, the default of True should be used. - socket -- Either 'mock' or 'live' to indicate if the socket - should be a dummy, mock socket or a live, functioning - socket. Defaults to 'mock'. - jid -- The JID to use for the connection. - Defaults to 'tester@localhost'. - password -- The password to use for the connection. - Defaults to 'test'. - server -- The name of the XMPP server. Defaults to 'localhost'. - port -- The port to use when connecting to the server. - Defaults to 5222. - plugins -- List of plugins to register. By default, all plugins - are loaded. - """ - if mode == 'client': - self.xmpp = ClientXMPP(jid, password, - sasl_mech=sasl_mech, - plugin_config=plugin_config) - elif mode == 'component': - self.xmpp = ComponentXMPP(jid, password, - server, port, - plugin_config=plugin_config) - else: - raise ValueError("Unknown XMPP connection mode.") - - # Remove unique ID prefix to make it easier to test - self.xmpp._id_prefix = '' - self.xmpp._disconnect_wait_for_threads = False - self.xmpp.default_lang = None - self.xmpp.peer_default_lang = None - - # We will use this to wait for the session_start event - # for live connections. - skip_queue = Queue() - - if socket == 'mock': - self.xmpp.set_socket(TestSocket()) - - # Simulate connecting for mock sockets. - self.xmpp.auto_reconnect = False - self.xmpp.state._set_state('connected') - - # Must have the stream header ready for xmpp.process() to work. - if not header: - header = self.xmpp.stream_header - self.xmpp.socket.recv_data(header) - elif socket == 'live': - self.xmpp.socket_class = TestLiveSocket - - def wait_for_session(x): - self.xmpp.socket.clear() - skip_queue.put('started') - - self.xmpp.add_event_handler('session_start', wait_for_session) - if server is not None: - self.xmpp.connect((server, port)) - else: - self.xmpp.connect() - else: - raise ValueError("Unknown socket type.") - - if plugins is None: - self.xmpp.register_plugins() - else: - for plugin in plugins: - self.xmpp.register_plugin(plugin) - - # Some plugins require messages to have ID values. Set - # this to True in tests related to those plugins. - self.xmpp.use_message_ids = False - - self.xmpp.process(threaded=True) - if skip: - if socket != 'live': - # Mark send queue as usable - self.xmpp.session_bind_event.set() - self.xmpp.session_started_event.set() - # Clear startup stanzas - self.xmpp.socket.next_sent(timeout=1) - if mode == 'component': - self.xmpp.socket.next_sent(timeout=1) - else: - skip_queue.get(block=True, timeout=10) - - def make_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - default_lang="en", - version="1.0", - xml_header=True): - """ - Create a stream header to be received by the test XMPP agent. - - The header must be saved and passed to stream_start. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - """ - header = '<stream:stream %s>' - parts = [] - if xml_header: - header = '<?xml version="1.0"?>' + header - if sto: - parts.append('to="%s"' % sto) - if sfrom: - parts.append('from="%s"' % sfrom) - if sid: - parts.append('id="%s"' % sid) - if default_lang: - parts.append('xml:lang="%s"' % default_lang) - parts.append('version="%s"' % version) - parts.append('xmlns:stream="%s"' % stream_ns) - parts.append('xmlns="%s"' % default_ns) - return header % ' '.join(parts) - - def recv(self, data, defaults=[], method='exact', - use_values=True, timeout=1): - """ - Pass data to the dummy XMPP client as if it came from an XMPP server. - - If using a live connection, verify what the server has sent. - - Arguments: - data -- If a dummy socket is being used, the XML that is to - be received next. Otherwise it is the criteria used - to match against live data that is received. - defaults -- A list of stanza interfaces with default values that - may interfere with comparisons. - method -- Select the type of comparison to use for - verifying the received stanza. Options are 'exact', - 'id', 'stanzapath', 'xpath', and 'mask'. - Defaults to the value of self.match_method. - use_values -- Indicates if stanza comparisons should test using - stanza.values. Defaults to True. - timeout -- Time to wait in seconds for data to be received by - a live connection. - """ - if self.xmpp.socket.is_live: - # we are working with a live connection, so we should - # verify what has been received instead of simulating - # receiving data. - recv_data = self.xmpp.socket.next_recv(timeout) - if recv_data is None: - self.fail("No stanza was received.") - xml = self.parse_xml(recv_data) - self.fix_namespaces(xml, 'jabber:client') - stanza = self.xmpp._build_stanza(xml, 'jabber:client') - self.check(stanza, data, - method=method, - defaults=defaults, - use_values=use_values) - else: - # place the data in the dummy socket receiving queue. - data = str(data) - self.xmpp.socket.recv_data(data) - - def recv_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=False, - timeout=1): - """ - Check that a given stream header was received. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. Set to None to ignore. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - timeout -- Length of time to wait in seconds for a - response. - """ - header = self.make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - version=version, - xml_header=xml_header) - recv_header = self.xmpp.socket.next_recv(timeout) - if recv_header is None: - raise ValueError("Socket did not return data.") - - # Apply closing elements so that we can construct - # XML objects for comparison. - header2 = header + '</stream:stream>' - recv_header2 = recv_header + '</stream:stream>' - - xml = self.parse_xml(header2) - recv_xml = self.parse_xml(recv_header2) - - if sid is None: - # Ignore the id sent by the server since - # we can't know in advance what it will be. - if 'id' in recv_xml.attrib: - del recv_xml.attrib['id'] - - # Ignore the xml:lang attribute for now. - if 'xml:lang' in recv_xml.attrib: - del recv_xml.attrib['xml:lang'] - xml_ns = 'http://www.w3.org/XML/1998/namespace' - if '{%s}lang' % xml_ns in recv_xml.attrib: - del recv_xml.attrib['{%s}lang' % xml_ns] - - if list(recv_xml): - # We received more than just the header - for xml in recv_xml: - self.xmpp.socket.recv_data(tostring(xml)) - - attrib = recv_xml.attrib - recv_xml.clear() - recv_xml.attrib = attrib - - self.failUnless( - self.compare(xml, recv_xml), - "Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % ( - '%s %s' % (xml.tag, xml.attrib), - '%s %s' % (recv_xml.tag, recv_xml.attrib))) - - def recv_feature(self, data, method='mask', use_values=True, timeout=1): - """ - """ - if method is None and hasattr(self, 'match_method'): - method = getattr(self, 'match_method') - - if self.xmpp.socket.is_live: - # we are working with a live connection, so we should - # verify what has been received instead of simulating - # receiving data. - recv_data = self.xmpp.socket.next_recv(timeout) - xml = self.parse_xml(data) - recv_xml = self.parse_xml(recv_data) - if recv_data is None: - self.fail("No stanza was received.") - if method == 'exact': - self.failUnless(self.compare(xml, recv_xml), - "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( - tostring(xml), tostring(recv_xml))) - elif method == 'mask': - matcher = MatchXMLMask(xml) - self.failUnless(matcher.match(recv_xml), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % tostring(xml) + \ - "Stanza:\n%s" % tostring(recv_xml)) - else: - raise ValueError("Uknown matching method: %s" % method) - else: - # place the data in the dummy socket receiving queue. - data = str(data) - self.xmpp.socket.recv_data(data) - - def send_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - default_lang="en", - version="1.0", - xml_header=False, - timeout=1): - """ - Check that a given stream header was sent. - - Arguments: - sto -- The recipient of the stream header. - sfrom -- The agent sending the stream header. - sid -- The stream's id. - stream_ns -- The namespace of the stream's root element. - default_ns -- The default stanza namespace. - version -- The stream version. - xml_header -- Indicates if the XML version header should be - appended before the stream header. - timeout -- Length of time to wait in seconds for a - response. - """ - header = self.make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - default_lang=default_lang, - version=version, - xml_header=xml_header) - sent_header = self.xmpp.socket.next_sent(timeout) - if sent_header is None: - raise ValueError("Socket did not return data.") - - # Apply closing elements so that we can construct - # XML objects for comparison. - header2 = header + '</stream:stream>' - sent_header2 = sent_header + b'</stream:stream>' - - xml = self.parse_xml(header2) - sent_xml = self.parse_xml(sent_header2) - - self.failUnless( - self.compare(xml, sent_xml), - "Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % ( - header, sent_header)) - - def send_feature(self, data, method='mask', use_values=True, timeout=1): - """ - """ - sent_data = self.xmpp.socket.next_sent(timeout) - xml = self.parse_xml(data) - sent_xml = self.parse_xml(sent_data) - if sent_data is None: - self.fail("No stanza was sent.") - if method == 'exact': - self.failUnless(self.compare(xml, sent_xml), - "Features do not match.\nDesired:\n%s\nReceived:\n%s" % ( - tostring(xml), tostring(sent_xml))) - elif method == 'mask': - matcher = MatchXMLMask(xml) - self.failUnless(matcher.match(sent_xml), - "Stanza did not match using %s method:\n" % method + \ - "Criteria:\n%s\n" % tostring(xml) + \ - "Stanza:\n%s" % tostring(sent_xml)) - else: - raise ValueError("Uknown matching method: %s" % method) - - def send(self, data, defaults=None, use_values=True, - timeout=.5, method='exact'): - """ - Check that the XMPP client sent the given stanza XML. - - Extracts the next sent stanza and compares it with the given - XML using check. - - Arguments: - stanza_class -- The class of the sent stanza object. - data -- The XML string of the expected Message stanza, - or an equivalent stanza object. - use_values -- Modifies the type of tests used by check_message. - defaults -- A list of stanza interfaces that have defaults - values which may interfere with comparisons. - timeout -- Time in seconds to wait for a stanza before - failing the check. - method -- Select the type of comparison to use for - verifying the sent stanza. Options are 'exact', - 'id', 'stanzapath', 'xpath', and 'mask'. - Defaults to the value of self.match_method. - """ - sent = self.xmpp.socket.next_sent(timeout) - if data is None and sent is None: - return - if data is None and sent is not None: - self.fail("Stanza data was sent: %s" % sent) - if sent is None: - self.fail("No stanza was sent.") - - xml = self.parse_xml(sent) - self.fix_namespaces(xml, 'jabber:client') - sent = self.xmpp._build_stanza(xml, 'jabber:client') - self.check(sent, data, - method=method, - defaults=defaults, - use_values=use_values) - - def stream_close(self): - """ - Disconnect the dummy XMPP client. - - Can be safely called even if stream_start has not been called. - - Must be placed in the tearDown method of a test class to ensure - that the XMPP client is disconnected after an error. - """ - if hasattr(self, 'xmpp') and self.xmpp is not None: - self.xmpp.socket.recv_data(self.xmpp.stream_footer) - self.xmpp.disconnect() - - # ------------------------------------------------------------------ - # XML Comparison and Cleanup - - def fix_namespaces(self, xml, ns): - """ - Assign a namespace to an element and any children that - don't have a namespace. - - Arguments: - xml -- The XML object to fix. - ns -- The namespace to add to the XML object. - """ - if xml.tag.startswith('{'): - return - xml.tag = '{%s}%s' % (ns, xml.tag) - for child in xml: - self.fix_namespaces(child, ns) - - def compare(self, xml, *other): - """ - Compare XML objects. - - Arguments: - xml -- The XML object to compare against. - *other -- The list of XML objects to compare. - """ - if not other: - return False - - # Compare multiple objects - if len(other) > 1: - for xml2 in other: - if not self.compare(xml, xml2): - return False - return True - - other = other[0] - - # Step 1: Check tags - if xml.tag != other.tag: - return False - - # Step 2: Check attributes - if xml.attrib != other.attrib: - return False - - # Step 3: Check text - if xml.text is None: - xml.text = "" - if other.text is None: - other.text = "" - xml.text = xml.text.strip() - other.text = other.text.strip() - - if xml.text != other.text: - return False - - # Step 4: Check children count - if len(list(xml)) != len(list(other)): - return False - - # Step 5: Recursively check children - for child in xml: - child2s = other.findall("%s" % child.tag) - if child2s is None: - return False - for child2 in child2s: - if self.compare(child, child2): - break - else: - return False - - # Step 6: Recursively check children the other way. - for child in other: - child2s = xml.findall("%s" % child.tag) - if child2s is None: - return False - for child2 in child2s: - if self.compare(child, child2): - break - else: - return False - - # Everything matches - return True |