From b83012d1011ba6bb7737c3b738a0bee515e3b438 Mon Sep 17 00:00:00 2001
From: Inso <insomniak.fr@gmail.com>
Date: Wed, 23 Sep 2015 23:43:51 +0200
Subject: [PATCH] Implementing the feature #214

---
 src/cutecoin/core/account.py                  |  89 +++++++++++-
 src/cutecoin/core/registry/identities.py      |   3 +-
 src/cutecoin/gui/process_cfg_community.py     |  18 ++-
 .../test_add_community.py                     | 133 +++++++++++++++---
 src/cutecoin/tests/qapp.py                    |   2 +-
 5 files changed, 213 insertions(+), 32 deletions(-)

diff --git a/src/cutecoin/core/account.py b/src/cutecoin/core/account.py
index 3576096a..bb24fd9d 100644
--- a/src/cutecoin/core/account.py
+++ b/src/cutecoin/core/account.py
@@ -19,10 +19,11 @@ from . import money
 from .wallet import Wallet
 from .community import Community
 from .registry import LocalState
-from ..tools.exceptions import ContactAlreadyExists
+from ..tools.exceptions import ContactAlreadyExists, NoPeerAvailable
 from ..tools.decorators import asyncify
 from ucoinpy.api import bma
 from ucoinpy.api.bma import PROTOCOL_VERSION
+from aiohttp.errors import ClientError
 
 
 class Account(QObject):
@@ -270,6 +271,92 @@ class Account(QObject):
             value += val
         return value
 
+    @asyncio.coroutine
+    def check_registered(self, community):
+        """
+        Checks for the pubkey and the uid of an account in a community
+        :param cutecoin.core.Community community: The community we check for registration
+        :return: (True if found, local value, network value)
+        """
+        def _parse_uid_certifiers(data):
+            return self.name == data['uid'], self.name, data['uid']
+
+        def _parse_uid_lookup(data):
+            timestamp = 0
+            found_uid = ""
+            for result in data['results']:
+                if result["pubkey"] == self.pubkey:
+                    uids = result['uids']
+                    for uid_data in uids:
+                        if uid_data["meta"]["timestamp"] > timestamp:
+                            timestamp = uid_data["meta"]["timestamp"]
+                            found_uid = uid_data["uid"]
+            return self.name == found_uid, self.name, found_uid
+
+        def _parse_pubkey_certifiers(data):
+            return self.pubkey == data['pubkey'], self.pubkey, data['pubkey']
+
+        def _parse_pubkey_lookup(data):
+            timestamp = 0
+            found_uid = ""
+            found_result = ["", ""]
+            for result in data['results']:
+                uids = result['uids']
+                for uid_data in uids:
+                    if uid_data["meta"]["timestamp"] > timestamp:
+                        timestamp = uid_data["meta"]["timestamp"]
+                        found_uid = uid_data["uid"]
+                if found_uid == self.name:
+                    found_result = result['pubkey'], found_uid
+            if found_result[1] == self.name:
+                return self.pubkey == found_result[0], self.pubkey, found_result[0]
+            else:
+                return False, self.pubkey, None
+
+        @asyncio.coroutine
+        def execute_requests(parsers, search):
+            tries = 0
+            request = bma.wot.CertifiersOf
+            nonlocal registered
+            while tries < 3 and not registered[0] and not registered[2]:
+                try:
+                    data = yield from community.bma_access.simple_request(request,
+                                                                          req_args={'search': search})
+                    registered = parsers[request](data)
+                except ValueError as e:
+                    if '404' in str(e) or '400' in str(e):
+                        if request == bma.wot.CertifiersOf:
+                            request = bma.wot.Lookup
+                            tries = 0
+                        else:
+                            tries += 1
+                    else:
+                        tries += 1
+                except asyncio.TimeoutError:
+                    tries += 1
+                except ClientError:
+                    tries += 1
+
+        registered = (False, self.name, None)
+        # We execute search based on pubkey
+        # And look for account UID
+        uid_parsers = {
+                    bma.wot.CertifiersOf: _parse_uid_certifiers,
+                    bma.wot.Lookup: _parse_uid_lookup
+                   }
+        yield from execute_requests(uid_parsers, self.pubkey)
+
+        # If the uid wasn't found when looking for the pubkey
+        # We look for the uid and check for the pubkey
+        if not registered[0] and not registered[2]:
+            pubkey_parsers = {
+                        bma.wot.CertifiersOf: _parse_pubkey_certifiers,
+                        bma.wot.Lookup: _parse_pubkey_lookup
+                       }
+            yield from execute_requests(pubkey_parsers, self.name)
+
+        return registered
+
     @asyncio.coroutine
     def send_selfcert(self, password, community):
         """
diff --git a/src/cutecoin/core/registry/identities.py b/src/cutecoin/core/registry/identities.py
index fdb8ff10..5a97deb7 100644
--- a/src/cutecoin/core/registry/identities.py
+++ b/src/cutecoin/core/registry/identities.py
@@ -86,7 +86,8 @@ class IdentitiesRegistry:
             tries = 0
             while tries < 3 and identity.local_state == LocalState.NOT_FOUND:
                 try:
-                    data = yield from community.bma_access.simple_request(bma.wot.CertifiersOf, req_args={'search': pubkey})
+                    data = yield from community.bma_access.simple_request(bma.wot.CertifiersOf,
+                                                                          req_args={'search': pubkey})
                     identity.uid = data['uid']
                     identity.local_state = LocalState.PARTIAL
                     identity.blockchain_state = BlockchainState.VALIDATED
diff --git a/src/cutecoin/gui/process_cfg_community.py b/src/cutecoin/gui/process_cfg_community.py
index 2dd0cc19..8c4496c5 100644
--- a/src/cutecoin/gui/process_cfg_community.py
+++ b/src/cutecoin/gui/process_cfg_community.py
@@ -16,6 +16,7 @@ from ..models.peering import PeeringTreeModel
 from ..core import Community
 from ..core.registry.identity import BlockchainState
 from ..core.net import Node
+from ..tools.decorators import asyncify
 from . import toast
 
 
@@ -62,9 +63,12 @@ class StepPageInit(Step):
         self.node = yield from Node.from_address(None, server, port)
         if self.node:
             community = Community.create(self.node)
-            identity = yield from self.app.identities_registry.future_find(self.account.pubkey, community)
-            if identity.blockchain_state == BlockchainState.NOT_FOUND:
+            registered = yield from self.account.check_registered(community)
+            if registered[0] is False and registered[2] is None:
                 self.config_dialog.label_error.setText(self.tr("Could not find your identity on the network."))
+            elif registered[0] is False and registered[2]:
+                self.config_dialog.label_error.setText(self.tr("""Your pubkey or UID is different on the network.
+Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
             else:
                 self.config_dialog.community = community
                 self.config_dialog.next()
@@ -84,8 +88,8 @@ class StepPageInit(Step):
         self.node = yield from Node.from_address(None, server, port)
         if self.node:
             community = Community.create(self.node)
-            identity = yield from self.app.identities_registry.future_find(self.account.pubkey, community)
-            if identity.blockchain_state == BlockchainState.NOT_FOUND:
+            registered = yield from self.account.check_registered(community)
+            if registered[0] is False and registered[2] is None:
                 password = yield from self.password_asker.async_exec()
                 if self.password_asker.result() == QDialog.Rejected:
                     return
@@ -102,10 +106,12 @@ class StepPageInit(Step):
                     if self.app.preferences['notifications']:
                         toast.display(self.tr("Error"), self.tr("{0}".format(result[1])))
                 QApplication.restoreOverrideCursor()
-
                 self.config_dialog.community = community
+            elif registered[0] is False and registered[2]:
+                self.config_dialog.label_error.setText(self.tr("""Your pubkey or UID was already found on the network.
+Yours : {0}, the network : {1}""".format(registered[1], registered[2])))
             else:
-                self.config_dialog.label_error.setText(self.tr("Pubkey already exists on the network"))
+                self.config_dialog.label_error.setText(self.tr("Your account already exists on the network"))
         else:
             self.config_dialog.label_error.setText(self.tr("Could not connect."))
 
diff --git a/src/cutecoin/tests/gui/process_cfg_community/test_add_community.py b/src/cutecoin/tests/gui/process_cfg_community/test_add_community.py
index 52b8a97f..1e729591 100644
--- a/src/cutecoin/tests/gui/process_cfg_community/test_add_community.py
+++ b/src/cutecoin/tests/gui/process_cfg_community/test_add_community.py
@@ -24,7 +24,7 @@ class ProcessAddCommunity(unittest.TestCase):
         QLocale.setDefault(QLocale("en_GB"))
         self.lp = quamash.QEventLoop(self.qapplication)
         asyncio.set_event_loop(self.lp)
-        self.lp.set_exception_handler(lambda lp, ctx : unitttest_exception_handler(self, lp, ctx))
+        #self.lp.set_exception_handler(lambda lp, ctx : unitttest_exception_handler(self, lp, ctx))
         self.identities_registry = IdentitiesRegistry({})
 
         self.application = Application(self.qapplication, self.lp, self.identities_registry)
@@ -32,7 +32,7 @@ class ProcessAddCommunity(unittest.TestCase):
         # Salt/password : "testcutecoin/testcutecoin"
         # Pubkey : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ
         self.account = Account("testcutecoin", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                               "test", [], [], [], self.identities_registry)
+                               "john", [], [], [], self.identities_registry)
         self.password_asker = PasswordAskerDialog(self.account)
         self.password_asker.password = "testcutecoin"
         self.password_asker.remember = True
@@ -52,11 +52,6 @@ class ProcessAddCommunity(unittest.TestCase):
                                                     self.account,
                                                     None, self.password_asker)
 
-        @asyncio.coroutine
-        def open_dialog(process_community):
-            result = yield from process_community.async_exec()
-            self.assertEqual(result, QDialog.Accepted)
-
         def close_dialog():
             if process_community.isVisible():
                 process_community.close()
@@ -84,10 +79,17 @@ class ProcessAddCommunity(unittest.TestCase):
                 self.assertEqual(mock.get_request(i).method, 'GET')
                 self.assertEqual(mock.get_request(i).url,
                                  '/wot/lookup/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ')
-            yield from asyncio.sleep(1)
+            yield from asyncio.sleep(5)
+            self.assertEqual(mock.get_request(5).method, 'GET')
+            self.assertEqual(mock.get_request(5).url,
+                             '/wot/certifiers-of/john')
+            for i in range(6, 9):
+                self.assertEqual(mock.get_request(i).method, 'GET')
+                self.assertEqual(mock.get_request(i).url,
+                                 '/wot/lookup/john')
 
-            self.assertEqual(mock.get_request(5).method, 'POST')
-            self.assertEqual(mock.get_request(5).url[:8], '/wot/add')
+            self.assertEqual(mock.get_request(9).url[:8], '/wot/add')
+            self.assertEqual(mock.get_request(9).method, 'POST')
             self.assertEqual(process_community.label_error.text(), "Broadcasting identity...")
             yield from asyncio.sleep(1)
 
@@ -98,7 +100,8 @@ class ProcessAddCommunity(unittest.TestCase):
 
         self.lp.call_later(15, close_dialog)
         asyncio.async(exec_test())
-        self.lp.run_until_complete(open_dialog(process_community))
+        self.lp.run_until_complete(process_community.async_exec())
+        self.assertEqual(process_community.result(), QDialog.Accepted)
         mock.delete_mock()
 
     def test_connect_community_empty_blockchain(self):
@@ -110,11 +113,6 @@ class ProcessAddCommunity(unittest.TestCase):
                                                     self.account,
                                                     None, self.password_asker)
 
-        @asyncio.coroutine
-        def open_dialog(process_community):
-            result = yield from process_community.async_exec()
-            self.assertEqual(result, QDialog.Rejected)
-
         def close_dialog():
             if process_community.isVisible():
                 process_community.close()
@@ -132,9 +130,11 @@ class ProcessAddCommunity(unittest.TestCase):
             self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
             self.assertEqual(process_community.spinbox_port.value(), 50000)
             QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
-            yield from asyncio.sleep(1)
+            yield from asyncio.sleep(3)
+            self.assertNotEqual(mock.get_request(0), None)
             self.assertEqual(mock.get_request(0).method, 'GET')
             self.assertEqual(mock.get_request(0).url, '/network/peering')
+            self.assertNotEqual(mock.get_request(1), None)
             self.assertEqual(mock.get_request(1).method, 'GET')
             self.assertEqual(mock.get_request(1).url,
                              '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ')
@@ -150,22 +150,107 @@ class ProcessAddCommunity(unittest.TestCase):
 
         self.lp.call_later(15, close_dialog)
         asyncio.async(exec_test())
-        self.lp.run_until_complete(open_dialog(process_community))
+        self.lp.run_until_complete(process_community.async_exec())
         mock.delete_mock()
 
-    def test_connect_community_nice_blockchain(self):
+    def test_connect_community_wrong_pubkey(self):
         mock = nice_blockchain.get_mock()
         time.sleep(2)
         logging.debug(mock.pretend_url)
         API.reverse_url = pretender_reversed(mock.pretend_url)
+        self.account.pubkey = "wrong_pubkey"
         process_community = ProcessConfigureCommunity(self.application,
                                                     self.account,
                                                     None, self.password_asker)
 
+        def close_dialog():
+            if process_community.isVisible():
+                process_community.close()
+
         @asyncio.coroutine
-        def open_dialog(process_community):
-            result = yield from process_community.async_exec()
-            self.assertEqual(result, QDialog.Accepted)
+        def exec_test():
+            yield from asyncio.sleep(1)
+            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
+            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
+            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
+            process_community.spinbox_port.setValue(50000)
+            self.assertEqual(process_community.stacked_pages.currentWidget(),
+                             process_community.page_node,
+                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
+            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
+            self.assertEqual(process_community.spinbox_port.value(), 50000)
+            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
+            yield from asyncio.sleep(1)
+            self.assertNotEqual(mock.get_request(0), None)
+            self.assertEqual(mock.get_request(0).method, 'GET')
+            self.assertEqual(mock.get_request(0).url, '/network/peering')
+            self.assertNotEqual(mock.get_request(1), None)
+            self.assertEqual(mock.get_request(1).method, 'GET')
+            self.assertEqual(mock.get_request(1).url,
+                             '/wot/certifiers-of/wrong_pubkey')
+            self.assertEqual(process_community.label_error.text(), """Your pubkey or UID is different on the network.
+Yours : wrong_pubkey, the network : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ""")
+            process_community.close()
+
+        self.lp.call_later(15, close_dialog)
+        asyncio.async(exec_test())
+        self.lp.run_until_complete(process_community.async_exec())
+        self.assertEqual(process_community.result(), QDialog.Rejected)
+        mock.delete_mock()
+
+    def test_connect_community_wrong_uid(self):
+        mock = nice_blockchain.get_mock()
+        time.sleep(2)
+        logging.debug(mock.pretend_url)
+        API.reverse_url = pretender_reversed(mock.pretend_url)
+        self.account.name = "wrong_uid"
+        process_community = ProcessConfigureCommunity(self.application,
+                                                    self.account,
+                                                    None, self.password_asker)
+
+        def close_dialog():
+            if process_community.isVisible():
+                process_community.close()
+
+        @asyncio.coroutine
+        def exec_test():
+            yield from asyncio.sleep(1)
+            QTest.mouseClick(process_community.lineedit_server, Qt.LeftButton)
+            QTest.keyClicks(process_community.lineedit_server, "127.0.0.1")
+            QTest.mouseDClick(process_community.spinbox_port, Qt.LeftButton)
+            process_community.spinbox_port.setValue(50000)
+            self.assertEqual(process_community.stacked_pages.currentWidget(),
+                             process_community.page_node,
+                             msg="Current widget : {0}".format(process_community.stacked_pages.currentWidget().objectName()))
+            self.assertEqual(process_community.lineedit_server.text(), "127.0.0.1")
+            self.assertEqual(process_community.spinbox_port.value(), 50000)
+            QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
+            yield from asyncio.sleep(1)
+            self.assertNotEqual(mock.get_request(0), None)
+            self.assertEqual(mock.get_request(0).method, 'GET')
+            self.assertEqual(mock.get_request(0).url, '/network/peering')
+            self.assertNotEqual(mock.get_request(1), None)
+            self.assertEqual(mock.get_request(1).method, 'GET')
+            self.assertEqual(mock.get_request(1).url,
+                             '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ')
+            self.assertEqual(process_community.label_error.text(), """Your pubkey or UID  is different on the network.
+Yours : wrong_uid, the network : john""")
+            process_community.close()
+
+        self.lp.call_later(15, close_dialog)
+        asyncio.async(exec_test())
+        self.lp.run_until_complete(process_community.async_exec())
+        self.assertEqual(process_community.result(), QDialog.Rejected)
+        mock.delete_mock()
+
+    def test_connect_community_success(self):
+        mock = nice_blockchain.get_mock()
+        time.sleep(2)
+        logging.debug(mock.pretend_url)
+        API.reverse_url = pretender_reversed(mock.pretend_url)
+        process_community = ProcessConfigureCommunity(self.application,
+                                                    self.account,
+                                                    None, self.password_asker)
 
         def close_dialog():
             if process_community.isVisible():
@@ -185,8 +270,10 @@ class ProcessAddCommunity(unittest.TestCase):
             self.assertEqual(process_community.spinbox_port.value(), 50000)
             QTest.mouseClick(process_community.button_connect, Qt.LeftButton)
             yield from asyncio.sleep(1)
+            self.assertNotEqual(mock.get_request(0), None)
             self.assertEqual(mock.get_request(0).method, 'GET')
             self.assertEqual(mock.get_request(0).url, '/network/peering')
+            self.assertNotEqual(mock.get_request(1), None)
             self.assertEqual(mock.get_request(1).method, 'GET')
             self.assertEqual(mock.get_request(1).url,
                              '/wot/certifiers-of/7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ')
@@ -197,7 +284,7 @@ class ProcessAddCommunity(unittest.TestCase):
 
         self.lp.call_later(15, close_dialog)
         asyncio.async(exec_test())
-        self.lp.run_until_complete(open_dialog(process_community))
+        self.lp.run_until_complete(process_community.async_exec())
         mock.delete_mock()
 
 if __name__ == '__main__':
diff --git a/src/cutecoin/tests/qapp.py b/src/cutecoin/tests/qapp.py
index 0de485b7..708b11fa 100644
--- a/src/cutecoin/tests/qapp.py
+++ b/src/cutecoin/tests/qapp.py
@@ -24,7 +24,7 @@ def unitttest_exception_handler(test, loop, context):
     for key in [k for k in sorted(context) if k not in {'message', 'exception'}]:
         log_lines.append('{}: {!r}'.format(key, context[key]))
 
-    test.failureException('\n'.join(log_lines))
+    test.fail('\n'.join(log_lines))
 
 
 
-- 
GitLab