changeset 999:c37a24922f27

plugin XEP_0033: fixes the server part and the tests
author souliane <souliane@mailoo.org>
date Fri, 11 Apr 2014 11:02:42 +0200
parents f5761534e0f3
children 6f1e03068b5f
files src/core/sat_main.py src/plugins/plugin_xep_0033.py src/test/helpers.py src/test/test_plugin_xep_0033.py
diffstat 4 files changed, 88 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- a/src/core/sat_main.py	Wed Apr 23 12:01:59 2014 +0200
+++ b/src/core/sat_main.py	Fri Apr 11 11:02:42 2014 +0200
@@ -58,7 +58,7 @@
     history by the trigger, so the rest of the process should be stopped. This
     should normally be raised by the trigger with the minimal priority """
     def __init__(self, reason, mess_data):
-        Exception(reason)
+        Exception.__init__(self, reason)
         self.mess_data = mess_data  # added for testing purpose
 
 
--- a/src/plugins/plugin_xep_0033.py	Wed Apr 23 12:01:59 2014 +0200
+++ b/src/plugins/plugin_xep_0033.py	Fri Apr 11 11:02:42 2014 +0200
@@ -82,13 +82,14 @@
             if not 'address' in mess_data['extra']:
                 return mess_data
 
-            def discoCallback(entity):
-                if entity is None:
+            def discoCallback(entities):
+                if not entities:
                     return Failure(AbortSendMessage(_("XEP-0033 is being used but the server doesn't support it!")))
-                if mess_data["to"] != entity:
-                    log.warning(_("Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!") % {'expected': entity, 'current': mess_data["to"]})
-                    log.warning(_("TODO: addressing has be fixed by the backend... fix it in the frontend!"))
-                    mess_data["to"] = entity
+                if mess_data["to"] not in entities:
+                    expected = _(' or ').join([entity.userhost() for entity in entities])
+                    log.warning(_("Stanzas using XEP-0033 should be addressed to %(expected)s, not %(current)s!") % {'expected': expected, 'current': mess_data["to"]})
+                    log.warning(_("TODO: addressing has been fixed by the backend... fix it in the frontend!"))
+                    mess_data["to"] = list(entities)[0].userhostJID()
                 element = mess_data['xml'].addElement('addresses', NS_ADDRESS)
                 entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != '']
                 for type_, jid_ in entries:
@@ -96,7 +97,7 @@
                 # when the prosody plugin is completed, we can immediately return mess_data from here
                 self.sendAndStoreMessage(mess_data, entries, profile)
                 return Failure(MessageSentAndStored("XEP-0033 took over", mess_data))
-            d = self.host.requestServerDisco(NS_ADDRESS, profile_key=profile)
+            d = self.host.findFeaturesSet([NS_ADDRESS], profile_key=profile)
             d.addCallbacks(discoCallback, lambda dummy: discoCallback(None))
             return d
 
@@ -116,19 +117,22 @@
         - redesign the database to save only one entry to the database
         - change the newMessage signal to eventually pass more than one recipient
         """
-        def discoCallback(entity, to_jid):
-            new_data = copy.deepcopy(mess_data)
-            new_data['to'] = JID(to_jid)
-            new_data['xml']['to'] = to_jid
-            if entity:
-                if entity not in self.internal_data[timestamp]:
-                    self.host.sendAndStoreMessage(mess_data, False, profile)
-                    self.internal_data[timestamp].append(entity)
+        def discoCallback(entities, to_jid_s):
+            history_data = copy.deepcopy(mess_data)
+            history_data['to'] = JID(to_jid_s)
+            history_data['xml']['to'] = to_jid_s
+            if entities:
+                if entities not in self.internal_data[timestamp]:
+                    sent_data = copy.deepcopy(mess_data)
+                    sent_data['to'] = JID(JID(to_jid_s).host)
+                    sent_data['xml']['to'] = JID(to_jid_s).host
+                    self.host.sendAndStoreMessage(sent_data, False, profile)
+                    self.internal_data[timestamp].append(entities)
                 # we still need to fill the history and signal the echo...
-                self.host.sendAndStoreMessage(new_data, True, profile)
+                self.host.sendAndStoreMessage(history_data, True, profile)
             else:
                 # target server misses the addressing feature
-                self.host.sendAndStoreMessage(new_data, False, profile)
+                self.host.sendAndStoreMessage(history_data, False, profile)
 
         def errback(failure, to_jid):
             discoCallback(None, to_jid)
@@ -138,9 +142,9 @@
         defer_list = []
         for type_, jid_ in entries:
             d = defer.Deferred()
-            d.addCallback(self.host.requestServerDisco, JID(JID(jid_).host), profile_key=profile)
+            d.addCallback(self.host.findFeaturesSet, jid_=JID(JID(jid_).host), profile_key=profile)
             d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_])
-            d.callback(NS_ADDRESS)
+            d.callback([NS_ADDRESS])
             defer_list.append(d)
         d = defer.Deferred().addCallback(lambda dummy: self.internal_data.pop(timestamp))
         defer.DeferredList(defer_list).chainDeferred(d)
--- a/src/test/helpers.py	Wed Apr 23 12:01:59 2014 +0200
+++ b/src/test/helpers.py	Fri Apr 11 11:02:42 2014 +0200
@@ -93,7 +93,6 @@
         if not skip_send:
             self.sent_messages.append(mess_data["to"])
         self.stored_messages.append(mess_data["to"])
-        pass
 
     def getClient(self, profile_key):
         """Convenient method to get client from profile key
@@ -131,6 +130,33 @@
         entry = self.getSentMessageRaw(profile_index)
         return entry.toXml() if entry else None
 
+    def findFeaturesSet(self, features, category=None, type_=None, jid_=None, profile_key=None):
+        """Call self.addFeature from your tests to change the return value.
+
+        @return: a set of entities
+        """
+        client = self.getClient(profile_key)
+        if jid_ is None:
+            jid_ = JID(client.jid.host)
+        try:
+            if set(features).issubset(client.features[jid_]):
+                return defer.succeed(set([jid_]))
+        except (TypeError, AttributeError, KeyError):
+            pass
+        return defer.succeed(set())
+
+    def addFeature(self, jid_, feature, profile_key):
+        """Add a feature to an entity.
+
+        To be called from your tests when needed.
+        """
+        client = self.getClient(profile_key)
+        if not hasattr(client, 'features'):
+            client.features = {}
+        if jid_ not in client.features:
+            client.features[jid_] = set()
+        client.features[jid_].add(feature)
+
 
 class FakeBridge(object):
     """Class to simulate and test bridge calls"""
--- a/src/test/test_plugin_xep_0033.py	Wed Apr 23 12:01:59 2014 +0200
+++ b/src/test/test_plugin_xep_0033.py	Fri Apr 11 11:02:42 2014 +0200
@@ -31,9 +31,15 @@
 from twisted.words.protocols.jabber.jid import JID
 from logging import ERROR
 
+PROFILE = Const.PROFILE[0]
+JID_STR_FROM = Const.JID_STR[1]
+JID_STR_TO = Const.PROFILE_DICT[PROFILE].host
+JID_STR_X_TO = Const.JID_STR[0]
+JID_STR_X_CC = Const.JID_STR[1]
+JID_STR_X_BCC = Const.JID_STR[2]
+
 
 class XEP_0033Test(helpers.SatTestCase):
-    skip = "Must be fixed after disco changes"
 
     def setUp(self):
         self.host = helpers.FakeSAT()
@@ -50,15 +56,14 @@
                 <address type='bcc' jid='%s'/>
             </addresses>
         </message>
-        """ % (Const.JID_STR[1], self.host.getClientHostJid(Const.PROFILE[0]),
-               Const.JID_STR[0], Const.JID_STR[1], Const.JID_STR[2])
+        """ % (JID_STR_FROM, JID_STR_TO, JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC)
         stanza = parseXml(xml.encode("utf-8"))
         treatments = defer.Deferred()
-        self.plugin.messageReceivedTrigger(stanza, treatments, Const.PROFILE[0])
+        self.plugin.messageReceivedTrigger(stanza, treatments, PROFILE)
         data = {'extra': {}}
 
         def cb(data):
-            expected = ('to', Const.JID_STR[0], 'cc', Const.JID_STR[1], 'bcc', Const.JID_STR[2])
+            expected = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC)
             msg = 'Expected: %s\nGot:      %s' % (expected, data['extra']['addresses'])
             self.assertEqual(data['extra']['addresses'], '%s:%s\n%s:%s\n%s:%s\n' % expected, msg)
 
@@ -66,18 +71,18 @@
         treatments.callback(data)
 
     def test_sendMessageTrigger(self):
-        mess_data = {"to": self.host.getClientHostJid(Const.PROFILE[0]),
+        mess_data = {"to": JID(JID_STR_TO),
                      "type": "chat",
                      "message": "content",
                      "extra": {}
                      }
-        addresses = ('to', Const.JID_STR[0], 'cc', Const.JID_STR[1], 'bcc', Const.JID_STR[2])
+        addresses = ('to', JID_STR_X_TO, 'cc', JID_STR_X_CC, 'bcc', JID_STR_X_BCC)
         mess_data["extra"]["address"] = '%s:%s\n%s:%s\n%s:%s\n' % addresses
         original_stanza = u"""
         <message type="chat" from="%s" to="%s" id="test_1">
             <body>content</body>
         </message>
-        """ % (Const.JID_STR[1], self.host.getClientHostJid(Const.PROFILE[0]))
+        """ % (JID_STR_FROM, JID_STR_TO)
         mess_data['xml'] = parseXml(original_stanza.encode("utf-8"))
         expected = deepcopy(mess_data['xml'])
         addresses_extra = """
@@ -97,29 +102,28 @@
         def sendMessageErrback(failure, exception_class):
             """If the failure does encapsulate the expected exception, it will be silently
             trapped, otherwise it will be re-raised and will make the test fail"""
+            failure.trap(exception_class)
             if exception_class == MessageSentAndStored:
                 assertAddresses(failure.value.mess_data)
-            failure.trap(exception_class)
 
         def checkSentAndStored():
             """Check that all the recipients got their messages and that the history has been filled.
             /!\ see the comments in XEP_0033.sendAndStoreMessage"""
             sent = []
             stored = []
-            cache = set()
-            for to_s in [addresses[1], addresses[3], addresses[5]]:
+            for to_s in (JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC):
                 to_jid = JID(to_s)
                 host = JID(to_jid.host)
                 logger = getLogger()
                 level = logger.getEffectiveLevel()
                 logger.setLevel(ERROR)  # remove log.warning pollution
-                if self.host.memory.hasServerFeature(plugin.NS_ADDRESS, host, Const.PROFILE[0]):
-                    if host not in cache:
+                entities = yield self.host.findFeaturesSet([plugin.NS_ADDRESS], jid_=host, profile_key=PROFILE)
+                if host in entities:
+                    if host not in sent:  # send the message to the entity offering the feature
                         sent.append(host)
                         stored.append(host)
-                        cache.add(host)
-                    stored.append(to_jid)
-                else:
+                    stored.append(to_jid)  # store in history for each recipient
+                else:  # feature not supported, use normal behavior
                     sent.append(to_jid)
                     stored.append(to_jid)
                 logger.setLevel(level)
@@ -140,7 +144,7 @@
             logger.setLevel(ERROR)  # remove log.warning pollution
             pre_treatments = defer.Deferred()
             post_treatments = defer.Deferred()
-            self.plugin.sendMessageTrigger(data, pre_treatments, post_treatments, Const.PROFILE[0])
+            self.plugin.sendMessageTrigger(data, pre_treatments, post_treatments, PROFILE)
             post_treatments.callback(data)
             logger.setLevel(level)
             post_treatments.addCallbacks(assertAddresses, lambda failure: sendMessageErrback(failure, exception))
@@ -150,9 +154,20 @@
         data = deepcopy(mess_data)
         trigger(data, AbortSendMessage)
 
-        # feature is supported
+        # feature is supported by the main target server
         self.host.init()
         self.host.memory.init()
+        self.host.addFeature(JID(JID_STR_TO), plugin.NS_ADDRESS, PROFILE)
+        data = deepcopy(mess_data)
+        trigger(data, MessageSentAndStored)
+        checkSentAndStored()
+
+        # feature is supported by all target servers
+        self.host.init()
+        self.host.memory.init()
+        self.host.addFeature(JID(JID_STR_TO), plugin.NS_ADDRESS, PROFILE)
+        for dest in (JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC):
+            self.host.addFeature(JID(JID(dest).host), plugin.NS_ADDRESS, PROFILE)
         data = deepcopy(mess_data)
         trigger(data, MessageSentAndStored)
         checkSentAndStored()
@@ -160,7 +175,10 @@
         # check that a wrong recipient entity is fixed by the backend
         self.host.init()
         self.host.memory.init()
+        self.host.addFeature(JID(JID_STR_TO), plugin.NS_ADDRESS, PROFILE)
+        for dest in (JID_STR_X_TO, JID_STR_X_CC, JID_STR_X_BCC):
+            self.host.addFeature(JID(JID(dest).host), plugin.NS_ADDRESS, PROFILE)
         data = deepcopy(mess_data)
-        data["to"] = Const.JID[0]
+        data["to"] = JID(JID_STR_X_TO)
         trigger(data, MessageSentAndStored)
         checkSentAndStored()