diff --git a/run_tests.py b/run_tests.py index e99c4cf..1555de9 100644 --- a/run_tests.py +++ b/run_tests.py @@ -50,14 +50,14 @@ if __name__ == '__main__': feeder_suite = unittest.makeSuite(Feeder_TestCase, "test") sender_suite = unittest.makeSuite(Sender_TestCase, "test") jcl_suite = unittest.TestSuite() -# jcl_suite.addTest(FeederComponent_TestCase('test_handle_get_register_exist')) + jcl_suite.addTest(FeederComponent_TestCase('test_handle_set_register_new')) # jcl_suite.addTest(FeederComponent_TestCase('test_handle_presence_available_to_account_live_password')) # jcl_suite = unittest.TestSuite((feeder_component_suite)) # jcl_suite = unittest.TestSuite((component_suite)) - jcl_suite = unittest.TestSuite((component_suite, - feeder_component_suite, - feeder_suite, - sender_suite)) +# jcl_suite = unittest.TestSuite((component_suite, +# feeder_component_suite, +# feeder_suite, +# sender_suite)) test_support.run_suite(jcl_suite) diff --git a/src/jcl/jabber/component.py b/src/jcl/jabber/component.py index 5d79ec8..e1c5068 100644 --- a/src/jcl/jabber/component.py +++ b/src/jcl/jabber/component.py @@ -4,18 +4,18 @@ ## Login : David Rousselie ## Started on Wed Aug 9 21:04:42 2006 David Rousselie ## $Id$ -## +## ## Copyright (C) 2006 David Rousselie ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. -## +## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. -## +## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -56,7 +56,7 @@ VERSION = "0.1" ############################################################################### # JCL implementation ############################################################################### -class JCLComponent(Component): +class JCLComponent(Component): """Implement default JCL component behavior: - regular interval behavior - Jabber register process (add, delete, update accounts) @@ -80,7 +80,7 @@ class JCLComponent(Component): account_class = property(get_account_class, set_account_class) - + def __init__(self, jid, secret, @@ -106,7 +106,7 @@ class JCLComponent(Component): self.accounts = [] self.time_unit = 60 self.queue = Queue(100) - + self.disco_info.add_feature("jabber:iq:version") self.disco_info.add_feature("jabber:iq:register") self.__logger = logging.getLogger("jcl.jabber.JCLComponent") @@ -115,7 +115,7 @@ class JCLComponent(Component): signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) - + def run(self): """Main loop Connect to Jabber server @@ -323,10 +323,10 @@ class JCLComponent(Component): """Handle user registration response """ self.__logger.debug("SET_REGISTER") -## lang_class = \ -## self.__lang.get_lang_class_from_node(info_query.get_node()) + lang_class = \ + self.__lang.get_lang_class_from_node(info_query.get_node()) from_jid = info_query.get_from() -## base_from_jid = unicode(from_jid.bare()) + base_from_jid = unicode(from_jid.bare()) remove = info_query.xpath_eval("r:query/r:remove", \ {"r" : "jabber:iq:register"}) if remove: @@ -354,7 +354,55 @@ class JCLComponent(Component): x_data = X() x_data.from_xml(query.children) # TODO : get info from Xdata - + # "name" must not be null + name = x_data.get_field_value("name") + if name is None: + # TODO make error + print "ERROR" + self.db_connect() + accounts = self.account_class.select(\ + self.account_class.q.user_jid == base_from_jid \ + and self.account_class.q.name == name) + accounts_count = accounts.count() + all_accounts = self.account_class.select(\ + self.account_class.q.user_jid == base_from_jid) + all_accounts_count = all_accounts.count() + if accounts_count > 1: + # TODO make error + print "ERROR" + if accounts_count == 1: + account = list(accounts)[0] + else: + account = self.account_class(user_jid = base_from_jid, \ + name = name, \ + jid = name + u"@" + unicode(self.jid)) + for (field, field_type, field_post_func, field_default_func) in \ + self.account_class.get_register_fields(): + setattr(account, x_data.get_field_value(field, \ + field_post_func, \ + field_default_func)) + info_query = info_query.make_result_response() + self.stream.send(info_query) + + if all_accounts_count == 0: + self.stream.send(Presence(from_jid = self.jid, to_jid = base_from_jid, \ + stanza_type = "subscribe")) + if accounts_count == 0: + self.stream.send(Message(\ + from_jid = self.jid, to_jid = from_jid, \ + stanza_type = "normal", \ + subject = account.get_new_message_subject(lang_class), \ + body = account.get_new_message_body(lang_class))) + self.stream.send(Presence(from_jid = self.get_jid(account), \ + to_jid = base_from_jid, \ + stanza_type = "subscribe")) + else: + self.stream.send(Message(\ + from_jid = self.jid, to_jid = from_jid, \ + stanza_type = "normal", \ + subject = account.get_update_message_subject(lang_class), \ + body = account.get_update_message_body(lang_class))) + self.db_disconnect() def handle_presence_available(self, stanza): """Handle presence availability @@ -518,7 +566,7 @@ class JCLComponent(Component): and old_status == account.OFFLINE \ and _account.password == None : self._ask_password(_account, lang_class) - + def _ask_password(self, _account, lang_class): """Send a Jabber message to ask for account password """ @@ -551,6 +599,11 @@ class JCLComponent(Component): reg_form.instructions = lang_class.register_instructions reg_form.type = "form" + # "name" field is mandatory + reg_form.add_field(field_type = "text-single", \ + label = lang_class.account_name, \ + var = "name") + for (field, field_type) in \ self.account_class.get_register_fields(): lang_label_attr = self.account_class.__name__.lower() \ @@ -570,11 +623,13 @@ class JCLComponent(Component): """Return register form for an existing account (update) """ reg_form = self.get_reg_form(lang_class) + reg_form.fields["name"].value = account.name + reg_form.fields["name"].type = "hidden" for (field_name, field) in reg_form.fields.items(): if hasattr(self.account_class, field_name): field.value = getattr(account, field_name) return reg_form - + ########################################################################### # Virtual methods ########################################################################### diff --git a/src/jcl/jabber/x.py b/src/jcl/jabber/x.py index ee2d86e..f855834 100644 --- a/src/jcl/jabber/x.py +++ b/src/jcl/jabber/x.py @@ -50,7 +50,7 @@ class Field(object): """Jabber Xdata form Field """ def __init__(self, field_type, label, var, value): - self.__type = field_type + self.type = field_type self.__label = label self.__var = var self.value = value @@ -71,7 +71,7 @@ class Field(object): raise Exception, "parent field should not be None" else: field = parent.newChild(None, "field", None) - field.setProp("type", self.__type) + field.setProp("type", self.type) if not self.__label is None: field.setProp("label", self.__label) if not self.__var is None: @@ -106,6 +106,15 @@ class X(object): self.fields_tab.append(field) return field + def get_field_value(self, field_name, \ + post_func = (lambda value: value), \ + defaut_func = (lambda: None)): + """Return field value processed by post_func + or return default func processing if field does not exist""" + if self.fields.has_key(field_name): + return post_func(self.fields[field_name].value) + return default_func() + def attach_xml(self, info_query): """Attach this Xdata form to iq node """ diff --git a/src/jcl/lang.py b/src/jcl/lang.py index 74b3ae4..33e288e 100644 --- a/src/jcl/lang.py +++ b/src/jcl/lang.py @@ -88,12 +88,10 @@ class Lang: update_title = u"Jabber mail connection update" update_instructions = u"Modifying connection '%s'" connection_label = u"%s connection '%s'" - update_account_message_subject = u"Updated %s connection '%s'" - update_account_message_body = u"Registered with username '%s' and " \ - u"password '%s' on '%s'" - new_account_message_subject = u"New %s connection '%s' created" - new_account_message_body = u"Registered with " \ - "username '%s' and password '%s' on '%s'" + update_account_message_subject = u"Updated account '%s'" + update_account_message_body = u"Updated account" + new_account_message_subject = u"New account '%s' created" + new_account_message_body = u"New account created" ask_password_subject = u"Password request" ask_password_body = u"Reply to this message with the password " \ "for the following account: \n" \ @@ -135,13 +133,11 @@ class Lang: update_title = u"Mise à jour du compte JMC" update_instructions = u"Modification de la connexion '%s'" connection_label = u"Connexion %s '%s'" - update_account_message_subject = u"La connexion %s '%s' a été mise " \ + update_account_message_subject = u"Le compte '%s' a été mis " \ u"à jour" - update_account_message_body = u"Nom d'utilisateur : '%s'\nMot de " \ - u"passe : '%s'\nsur : '%s'" - new_account_message_subject = u"La connexion %s '%s' a été créée" - new_account_message_body = u"Nom d'utilisateur : '%s'\nMot de passe " \ - u": '%s'\nsur : '%s'" + update_account_message_body = u"Compte mis à jour" + new_account_message_subject = u"Le compte '%s' a été créé" + new_account_message_body = u"Compte créé" ask_password_subject = u"Demande de mot de passe" ask_password_body = u"Répondre à ce message avec le mot de passe " \ u"du compte suivant : \n" \ diff --git a/src/jcl/model/account.py b/src/jcl/model/account.py index a0d9c9a..b93b277 100644 --- a/src/jcl/model/account.py +++ b/src/jcl/model/account.py @@ -91,6 +91,32 @@ class Account(SQLObject): def get_register_fields(cls): - return [('name', "text-single")] + """Return a list of tuples for X Data Form composition + A tuple is composed of: + - field_name: might be the name of one of the class attribut + - field_type: 'text-single', 'hidden', 'text-private', 'boolean', + 'list-single', ... + - field_post_func: function called to process received field + - field_default_func: function to return default value (or error if + field is mandatory) + """ + return [] # "name" field is mandatory get_register_fields = classmethod(get_register_fields) + + def get_new_message_subject(self, lang_class): + """Get localized message subject for new account""" + return lang_class.new_account_message_subject % (self.name) + + def get_new_message_body(self, lang_class): + """Return localized message body for new account""" + return lang_class.new_account_message_body + + def get_update_message_subject(self, lang_class): + """Return localized message subject for existing account""" + return lang_class.new_account_message_subject % (self.name) + + def get_update_message_body(self, lang_class): + """Return localized message body for existing account""" + return lang_class.new_account_message_body + diff --git a/tests/jcl/jabber/test_component.py b/tests/jcl/jabber/test_component.py index 3133def..706c862 100644 --- a/tests/jcl/jabber/test_component.py +++ b/tests/jcl/jabber/test_component.py @@ -41,6 +41,7 @@ from jcl.jabber.component import JCLComponent from jcl.model import account from jcl.model.account import Account from jcl.lang import Lang +from jcl.jabber.x import X DB_PATH = "/tmp/test.db" DB_URL = DB_PATH# + "?debug=1&debugThreading=1" @@ -362,7 +363,7 @@ class JCLComponent_TestCase(unittest.TestCase): self.assertEquals(len(fields), 1) self.assertEquals(len([field for field in fields \ - if field.prop("type") == "text-single" \ + if field.prop("type") == "hidden" \ and field.prop("var") == "name" \ and field.prop("label") == \ Lang.en.account_name]), \ @@ -373,9 +374,72 @@ class JCLComponent_TestCase(unittest.TestCase): self.assertEquals(len(value), 1) self.assertEquals(value[0].content, "account1") - def test_handle_set_register(self): + def test_handle_set_register_new(self): + self.comp.stream = MockStream() + self.comp.stream_class = MockStream + x_data = X() + x_data.xmlns = "jabber:x:data" + x_data.type = "submit" + x_data.add_field(field_type = "text-single", \ + var = "name", \ + value = "account1") + iq_set = Iq(stanza_type = "set", \ + from_jid = "user1@test.com", \ + to_jid = "jcl.test.com") + query = iq_set.new_query("jabber:iq:register") + x_data.attach_xml(query) + self.comp.handle_set_register(iq_set) + + account.hub.threadConnection = connectionForURI('sqlite://' + DB_URL) + accounts = self.comp.account_class.select(\ + self.comp.account_class.q.user_jid == "user1@test.com" \ + and self.comp.account_class.q.name == "account1") + self.assertEquals(accounts.count(), 1) + _account = accounts[0] + self.assertEquals(_account.user_jid, "user1@test.com") + self.assertEquals(_account.name, "account1") + self.assertEquals(_account.jid, "account1@jcl.test.com") + del account.hub.threadConnection + + stanza_sent = self.comp.stream.sent + print str([str(stanza.get_node()) for stanza in stanza_sent]) + self.assertEquals(len(stanza_sent), 4) + iq_result = stanza_sent[0] + self.assertTrue(isinstance(iq_result, Iq)) + self.assertEquals(iq_result.get_node().prop("type"), "result") + self.assertEquals(iq_result.get_from(), "jcl.test.com") + self.assertEquals(iq_result.get_to(), "user1@test.com") + + presence_component = stanza_sent[1] + self.assertTrue(isinstance(presence_component, Presence)) + self.assertEquals(presence_component.get_from(), "jcl.test.com") + self.assertEquals(presence_component.get_to(), "user1@test.com") + self.assertEquals(presence_component.get_node().prop("type"), \ + "subscribe") + + message = stanza_sent[2] + self.assertTrue(isinstance(message, Message)) + self.assertEquals(message.get_from(), "jcl.test.com") + self.assertEquals(message.get_to(), "user1@test.com") + self.assertEquals(message.get_subject(), \ + _account.get_new_message_subject(Lang.en)) + self.assertEquals(message.get_body(), \ + _account.get_new_message_body(Lang.en)) + + presence_account = stanza_sent[3] + self.assertTrue(isinstance(presence_account, Presence)) + self.assertEquals(presence_account.get_from(), "account1@jcl.test.com") + self.assertEquals(presence_account.get_to(), "user1@test.com") + self.assertEquals(presence_account.get_node().prop("type"), \ + "subscribe") + + + def test_handle_set_register_update(self): pass + def test_handle_set_register_remove(self): + pass + def test_handle_presence_available_to_component(self): self.comp.stream = MockStream() self.comp.stream_class = MockStream