From c033521602d81ee225192d1d86182d278af45269 Mon Sep 17 00:00:00 2001
From: Vincent Pelletier <vincent@nexedi.com>
Date: Wed, 1 Apr 2009 10:05:42 +0000
Subject: [PATCH] Initial import of ClientHandler class unit test. Note: there
 are 3 unimplemented tests (raising NotImplementedError).  test_StopOperation:
 looks unimplemented in current code (no action besides logging). 
 test_knownMasterNotifyNodeInformation &
 test_unknownMasterNotifyNodeInformation: it is yet unsure on how to test
 those cases.

git-svn-id: https://svn.erp5.org/repos/neo/branches/prototype3@309 71dcc9de-d417-0410-9af5-da40c76e7ee4
---
 neo/client/tests/testClientHandler.py | 1028 +++++++++++++++++++++++++
 1 file changed, 1028 insertions(+)
 create mode 100644 neo/client/tests/testClientHandler.py

diff --git a/neo/client/tests/testClientHandler.py b/neo/client/tests/testClientHandler.py
new file mode 100644
index 00000000..489f7eb8
--- /dev/null
+++ b/neo/client/tests/testClientHandler.py
@@ -0,0 +1,1028 @@
+#
+# Copyright (C) 2009  Nexedi SA
+# 
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+import os
+import unittest
+import logging
+from mock import Mock, ReturnValues
+from neo.protocol import Packet, INVALID_UUID
+from neo.protocol import ERROR, REQUEST_NODE_IDENTIFICATION, ACCEPT_NODE_IDENTIFICATION, \
+     PING, PONG, ASK_PRIMARY_MASTER, ANSWER_PRIMARY_MASTER, ANNOUNCE_PRIMARY_MASTER, \
+     REELECT_PRIMARY_MASTER, NOTIFY_NODE_INFORMATION, START_OPERATION, \
+     STOP_OPERATION, ASK_LAST_IDS, ANSWER_LAST_IDS, ASK_PARTITION_TABLE, \
+     ANSWER_PARTITION_TABLE, SEND_PARTITION_TABLE, NOTIFY_PARTITION_CHANGES, \
+     ASK_UNFINISHED_TRANSACTIONS, ANSWER_UNFINISHED_TRANSACTIONS, \
+     ASK_OBJECT_PRESENT, ANSWER_OBJECT_PRESENT, \
+     DELETE_TRANSACTION, COMMIT_TRANSACTION, ASK_NEW_TID, ANSWER_NEW_TID, \
+     FINISH_TRANSACTION, NOTIFY_TRANSACTION_FINISHED, LOCK_INFORMATION, \
+     NOTIFY_INFORMATION_LOCKED, INVALIDATE_OBJECTS, UNLOCK_INFORMATION, \
+     ASK_NEW_OIDS, ANSWER_NEW_OIDS, ASK_STORE_OBJECT, ANSWER_STORE_OBJECT, \
+     ABORT_TRANSACTION, ASK_STORE_TRANSACTION, ANSWER_STORE_TRANSACTION, \
+     ASK_OBJECT, ANSWER_OBJECT, ASK_TIDS, ANSWER_TIDS, ASK_TRANSACTION_INFORMATION, \
+     ANSWER_TRANSACTION_INFORMATION, ASK_OBJECT_HISTORY, ANSWER_OBJECT_HISTORY, \
+     ASK_OIDS, ANSWER_OIDS, \
+     NOT_READY_CODE, OID_NOT_FOUND_CODE, SERIAL_NOT_FOUND_CODE, TID_NOT_FOUND_CODE, \
+     PROTOCOL_ERROR_CODE, TIMEOUT_ERROR_CODE, BROKEN_NODE_DISALLOWED_CODE, \
+     INTERNAL_ERROR_CODE, \
+     STORAGE_NODE_TYPE, CLIENT_NODE_TYPE, MASTER_NODE_TYPE, \
+     RUNNING_STATE, BROKEN_STATE, TEMPORARILY_DOWN_STATE, DOWN_STATE, \
+     UP_TO_DATE_STATE, OUT_OF_DATE_STATE, FEEDING_STATE, DISCARDED_STATE
+from neo.exception import ElectionFailure
+
+from neo.client.handler import ClientEventHandler
+from neo.node import StorageNode
+
+MARKER = []
+
+class ClientEventHandlerTest(unittest.TestCase):
+
+    def setUp(self):
+        # Silence all log messages
+        logging.basicConfig(level=logging.CRITICAL + 1)
+
+#    def tearDown(self):
+#        pass
+
+    def getConnection(self, uuid=None, port=10010, next_id=None, ip='127.0.0.1'):
+        if uuid is None:
+            uuid = self.getUUID()
+        return Mock({'addPacket': None,
+                     'getUUID': uuid,
+                     'getAddress': (ip, port),
+                     'getNextId': next_id,
+                     'lock': None,
+                     'unlock': None})
+
+    def getUUID(self):
+        uuid = INVALID_UUID
+        while uuid == INVALID_UUID:
+            uuid = os.urandom(16)
+        self.uuid = uuid
+        return uuid
+
+    def getDispatcher(self, queue=None):
+      return Mock({'getQueue': queue, 'connectToPrimaryMasterNode': None})
+
+    def test_ping(self):
+        """
+        Simplest test: check that a PING packet is answered by a PONG
+        packet.
+        """
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(None, dispatcher)
+        conn = self.getConnection()
+        client_handler.packetReceived(conn, Packet().ping(1))
+        pong = conn.mockGetNamedCalls('addPacket')[0].getParam(0)
+        self.assertTrue(isinstance(pong, Packet))
+        self.assertEquals(pong.getType(), PONG)
+
+    def _testInitialMasterWithMethod(self, method):
+        class App:
+            primary_master_node = None
+        app = App()
+        method(self.getDispatcher(), app)
+        self.assertEqual(app.primary_master_node, -1)
+
+    def _testMasterWithMethod(self, method):
+        uuid = self.getUUID()
+        class App:
+            primary_master_node = Mock({'getUUID': uuid})
+            master_conn = Mock({'close': None, 'getUUID': uuid})
+        app = App()
+        dispatcher = self.getDispatcher()
+        method(dispatcher, app, uuid=uuid)
+        # XXX: should connection closure be tested ? It's not implemented in all cases
+        #self.assertEquals(len(App.master_conn.mockGetNamedCalls('close')), 1)
+        #self.assertEquals(app.master_conn, None)
+        #self.assertEquals(app.primary_master_node, None)
+        self.assertEquals(len(dispatcher.mockGetNamedCalls('connectToPrimaryMasterNode')), 1)
+
+    def _testStorageWithMethod(self, method, state=TEMPORARILY_DOWN_STATE):
+        storage_ip = '127.0.0.1'
+        storage_port = 10011
+        fake_storage_node_uuid = self.getUUID()
+        # XXX: Must be replaced by a Mock object, but application uses isinstance...
+        fake_storage_node = StorageNode(uuid=fake_storage_node_uuid, server=(storage_ip, storage_port))
+        master_node_next_packet_id = 1
+        class App:
+            primary_master_node = Mock({'getUUID': self.getUUID()})
+            nm = Mock({'getNodeByServer': fake_storage_node})
+            cp = Mock({'removeConnection': None}) # TODO: Add expectation on parameter value (must be fake_storage_node)
+            master_conn = self.getConnection(next_id=ReturnValues(master_node_next_packet_id))
+        app = App()
+        conn = self.getConnection(port=storage_port, ip=storage_ip)
+        key_1 = (id(conn), 0)
+        queue_1 = Mock({'put': None, '__hash__': 1}) # TODO: Add expectation on parameter value (must be key_1)
+        # Fake another Storage connection by adding 1 to id(conn)
+        key_2 = (id(conn) + 1, 0)
+        queue_2 = Mock({'put': None, '__hash__': 2})
+        class Dispatcher:
+            message_table = {key_1: queue_1,
+                             key_2: queue_2}
+        dispatcher = Dispatcher()
+        method(dispatcher, app, conn=conn)
+        # Check that master was notified of the failure
+        addPacket_call_list = app.master_conn.mockGetNamedCalls('addPacket')
+          # Test sanity check
+        self.assertEqual(len(addPacket_call_list), 1)
+        node_status_packet = addPacket_call_list[0].getParam(0)
+        self.assertTrue(isinstance(node_status_packet, Packet))
+          # Test sanity check
+        self.assertEquals(node_status_packet.getId(), master_node_next_packet_id)
+        self.assertEquals(node_status_packet.getType(), NOTIFY_NODE_INFORMATION)
+        self.assertEquals(node_status_packet.decode()[0],
+                          [(STORAGE_NODE_TYPE, storage_ip, storage_port,
+                            fake_storage_node_uuid, state), ]
+        )
+        # Check that failed connection got removed from connection pool
+        removeConnection_call_list = app.cp.mockGetNamedCalls('removeConnection')
+          # Test sanity check
+        self.assertEqual(len(removeConnection_call_list), 1)
+        self.assertTrue(removeConnection_call_list[0].getParam(0) is fake_storage_node)
+        # Check that fake packet was put into queue_1, and none in queue_2.
+        queue_1_put_call_list = queue_1.mockGetNamedCalls('put')
+        self.assertEqual(len(queue_1_put_call_list), 1)
+        self.assertEqual(queue_1_put_call_list[0].getParam(0), (conn, None))
+        self.assertEqual(len(queue_2.mockGetNamedCalls('put')), 0)
+
+
+    def _testConnectionFailed(self, dispatcher, app, uuid=None, conn=None):
+        client_handler = ClientEventHandler(app, dispatcher)
+        if conn is None:
+            conn = self.getConnection(uuid=uuid)
+        client_handler.connectionFailed(conn)
+
+    def test_initialMasterConnectionFailed(self):
+        self._testInitialMasterWithMethod(self._testConnectionFailed)
+
+    def test_masterConnectionFailed(self):
+        self._testMasterWithMethod(self._testConnectionFailed)
+
+    def test_storageConnectionFailed(self):
+        self._testStorageWithMethod(self._testConnectionFailed)
+
+    def _testConnectionClosed(self, dispatcher, app, uuid=None, conn=None):
+        client_handler = ClientEventHandler(app, dispatcher)
+        if conn is None:
+            conn = self.getConnection(uuid=uuid)
+        client_handler.connectionClosed(conn)
+
+    def test_initialMasterConnectionClosed(self):
+        self._testInitialMasterWithMethod(self._testConnectionClosed)
+
+    def test_masterConnectionClosed(self):
+        self._testMasterWithMethod(self._testConnectionClosed)
+
+    def test_storageConnectionClosed(self):
+        self._testStorageWithMethod(self._testConnectionClosed)
+
+    def _testTimeoutExpired(self, dispatcher, app, uuid=None, conn=None):
+        client_handler = ClientEventHandler(app, dispatcher)
+        if conn is None:
+            conn = self.getConnection(uuid=uuid)
+        client_handler.timeoutExpired(conn)
+
+    def test_initialMasterTimeoutExpired(self):
+        self._testInitialMasterWithMethod(self._testTimeoutExpired)
+
+    def test_masterTimeoutExpired(self):
+        self._testMasterWithMethod(self._testTimeoutExpired)
+
+    def test_storageTimeoutExpired(self):
+        self._testStorageWithMethod(self._testTimeoutExpired)
+
+    def _testPeerBroken(self, dispatcher, app, uuid=None, conn=None):
+        client_handler = ClientEventHandler(app, dispatcher)
+        if conn is None:
+            conn = self.getConnection(uuid=uuid)
+        client_handler.peerBroken(conn)
+
+    def test_initialMasterPeerBroken(self):
+        self._testInitialMasterWithMethod(self._testPeerBroken)
+
+    def test_masterPeerBroken(self):
+        self._testMasterWithMethod(self._testPeerBroken)
+
+    def test_storagePeerBroken(self):
+        self._testStorageWithMethod(self._testPeerBroken, state=BROKEN_STATE)
+
+    def test_notReady(self):
+        class FakeLocal:
+            node_not_ready = None
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        client_handler.handleNotReady(conn, None, None)
+        self.assertEquals(app.local_var.node_not_ready, 1)
+
+
+    def test_clientAcceptNodeIdentification(self):
+        class App:
+            nm = Mock({'getNodeByServer': None})
+            storage_node = None
+            pt = None
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        uuid = self.getUUID()
+        client_handler.handleAcceptNodeIdentification(conn, None, CLIENT_NODE_TYPE,
+                                                      uuid, '127.0.0.1', 10010,
+                                                      0, 0)
+        self.assertEquals(len(conn.mockGetNamedCalls('close')), 1)
+        self.assertEquals(app.storage_node, None)
+        self.assertEquals(app.pt, None)
+
+    def test_masterAcceptNodeIdentification(self):
+        node = Mock({'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByServer': node})
+            storage_node = None
+            pt = None
+
+            def getQueue(self):
+                return None
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        uuid = self.getUUID()
+        client_handler.handleAcceptNodeIdentification(conn, None, MASTER_NODE_TYPE,
+                                                      uuid, '127.0.0.1', 10010,
+                                                      10, 2)
+        self.assertEquals(len(conn.mockGetNamedCalls('close')), 0)
+        self.assertEquals(len(conn.mockGetNamedCalls('setUUID')), 1)
+        setUUID_call_list = node.mockGetNamedCalls('setUUID')
+        self.assertEquals(len(setUUID_call_list), 1)
+        self.assertEquals(setUUID_call_list[0].getParam(0), uuid)
+        self.assertEquals(app.storage_node, None)
+        self.assertTrue(app.pt is not None)
+
+    def test_storageAcceptNodeIdentification(self):
+        node = Mock({'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByServer': node})
+            storage_node = None
+            pt = None
+
+            def getQueue(self):
+                return None
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        uuid = self.getUUID()
+        client_handler.handleAcceptNodeIdentification(conn, None, STORAGE_NODE_TYPE,
+                                                      uuid, '127.0.0.1', 10010,
+                                                      0, 0)
+        self.assertEquals(len(conn.mockGetNamedCalls('close')), 0)
+        self.assertEquals(len(conn.mockGetNamedCalls('setUUID')), 1)
+        setUUID_call_list = node.mockGetNamedCalls('setUUID')
+        self.assertEquals(len(setUUID_call_list), 1)
+        self.assertEquals(setUUID_call_list[0].getParam(0), uuid)
+        self.assertTrue(app.storage_node is node)
+        self.assertEquals(app.pt,  None)
+
+    def _testHandleUnexpectedPacketCalledWithMedhod(self, client_handler, method, args=(), kw=()):
+        # Monkey-patch handleUnexpectedPacket to check if it is called
+        call_list = []
+        def ClientHandler_handleUnexpectedPacket(self, conn, packet):
+            call_list.append((conn, packet))
+        original_handleUnexpectedPacket = client_handler.__class__.handleUnexpectedPacket
+        client_handler.__class__.handleUnexpectedPacket = ClientHandler_handleUnexpectedPacket
+        try:
+            method(*args, **dict(kw))
+        finally:
+            # Restore original method
+            client_handler.__class__.handleUnexpectedPacket = original_handleUnexpectedPacket
+        # Check that packet was notified as unexpected.
+        self.assertEquals(len(call_list), 1)
+        # Return call_list in case caller wants to do extra checks on it.
+        return call_list
+
+    # Master node handler
+    def test_initialAnswerPrimaryMaster(self):
+        client_handler = ClientEventHandler(None, self.getDispatcher())
+        conn = Mock({'getUUID': None})
+        call_list = self._testHandleUnexpectedPacketCalledWithMedhod(
+            client_handler, client_handler.handleAnswerPrimaryMaster,
+            args=(conn, None, 0, []))
+        # Nothing else happened, or a raise would have happened (app is None,
+        # and it's where things would happen)
+        self.assertEquals(call_list[0], (conn, None))
+        
+    def test_nonMasterAnswerPrimaryMaster(self):
+        for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
+            node = Mock({'getNodeType': node_type})
+            class App:
+                nm = Mock({'getNodeByUUID': node, 'getNodeByServer': None, 'add': None})
+            app = App()
+            client_handler = ClientEventHandler(app, self.getDispatcher())
+            conn = self.getConnection()
+            client_handler.handleAnswerPrimaryMaster(conn, None, 0, [])
+            # Check that nothing happened
+            self.assertEqual(len(app.nm.mockGetNamedCalls('getNodeByServer')), 0)
+            self.assertEqual(len(app.nm.mockGetNamedCalls('add')), 0)
+
+    def test_unknownNodeAnswerPrimaryMaster(self):
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': None, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID()
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        test_master_list = [('127.0.0.1', 10010, self.getUUID())]
+        client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
+        # Check that yet-unknown master node got added
+        getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
+        add_call_list = app.nm.mockGetNamedCalls('add')
+        self.assertEqual(len(getNodeByServer_call_list), 1)
+        self.assertEqual(len(add_call_list), 1)
+        address, port, test_uuid = test_master_list[0]
+        getNodeByServer_call = getNodeByServer_call_list[0]
+        add_call = add_call_list[0]
+        self.assertEquals((address, port), getNodeByServer_call.getParam(0))
+        node_instance = add_call.getParam(0)
+        self.assertEquals(test_uuid, node_instance.getUUID())
+        # Check that primary master was not updated (it is not known yet,
+        # hence INVALID_UUID in call).
+        self.assertEquals(app.primary_master_node, None)
+
+    def test_knownNodeUnknownUUIDNodeAnswerPrimaryMaster(self):
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': None, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID()
+            # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port)
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        test_node_uuid = self.getUUID()
+        test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
+        client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
+        # Check that known master node did not get added
+        getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
+        add_call_list = app.nm.mockGetNamedCalls('add')
+        self.assertEqual(len(getNodeByServer_call_list), 1)
+        self.assertEqual(len(add_call_list), 0)
+        # Check that node UUID got updated
+        setUUID_call_list = node.mockGetNamedCalls('setUUID')
+        self.assertEqual(len(setUUID_call_list), 1)
+        self.assertEqual(setUUID_call_list[0].getParam(0), test_node_uuid)
+        # Check that primary master was not updated (it is not known yet,
+        # hence INVALID_UUID in call).
+        self.assertEquals(app.primary_master_node, None)
+
+    def test_knownNodeKnownUUIDNodeAnswerPrimaryMaster(self):
+        test_node_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID()
+            # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port)
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
+        client_handler.handleAnswerPrimaryMaster(conn, None, INVALID_UUID, test_master_list)
+        # Check that known master node did not get added
+        getNodeByServer_call_list = app.nm.mockGetNamedCalls('getNodeByServer')
+        add_call_list = app.nm.mockGetNamedCalls('add')
+        self.assertEqual(len(getNodeByServer_call_list), 1)
+        self.assertEqual(len(add_call_list), 0)
+        # Check that node UUID was untouched
+        # XXX: should we just check that there was either no call or a call
+        # with same uuid, or enforce no call ? Here we enforce no call just
+        # because it's what implementation does.
+        setUUID_call_list = node.mockGetNamedCalls('setUUID')
+        self.assertEqual(len(setUUID_call_list), 0)
+        # Check that primary master was not updated (it is not known yet,
+        # hence INVALID_UUID in call).
+        self.assertEquals(app.primary_master_node, None)
+
+    # TODO: test known node, known but different uuid (not detected in code,
+    # desired behaviour unknown)
+
+    def test_alreadyDifferentPrimaryAnswerPrimaryMaster(self):
+        test_node_uuid = self.getUUID()
+        test_primary_node_uuid = test_node_uuid
+        while test_primary_node_uuid == test_node_uuid:
+            test_primary_node_uuid = self.getUUID()
+        test_primary_master_node = Mock({'getUUID': test_primary_node_uuid})
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID()
+            # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port)
+            primary_master_node = test_primary_master_node
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        # If primary master is already set *and* is not given primary master
+        # handle call raises.
+        # XXX: is it acceptable for a handle call to raise without any proper fallback ?
+        self.assertRaises(ElectionFailure, client_handler.handleAnswerPrimaryMaster,
+                          conn, None, test_node_uuid, [])
+
+    def test_alreadySamePrimaryAnswerPrimaryMaster(self):
+        test_node_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
+            primary_master_node = node
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, [])
+        # Check that primary node is (still) node.
+        self.assertTrue(app.primary_master_node is node)
+
+    def test_unknownNewPrimaryAnswerPrimaryMaster(self):
+        test_node_uuid = self.getUUID()
+        test_primary_node_uuid = test_node_uuid
+        while test_primary_node_uuid == test_node_uuid:
+            test_primary_node_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': ReturnValues(node, None), 'getNodeByServer': node, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID(), then test_primary_node_uuid
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        client_handler.handleAnswerPrimaryMaster(conn, None, test_primary_node_uuid, [])
+        # Check that primary node was not updated.
+        self.assertTrue(app.primary_master_node is None)
+
+    def test_AnswerPrimaryMaster(self):
+        test_node_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_node_uuid, 'setUUID': None})
+        class App:
+            nm = Mock({'getNodeByUUID': node, 'getNodeByServer': node, 'add': None})
+            # TODO: add an expectation on getNodeByUUID parameter: must be conn.getUUID()
+            # TODO: add an expectation on getNodeByServer parameter: must be (ip_address, port)
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        test_master_list = [('127.0.0.1', 10010, test_node_uuid)]
+        client_handler.handleAnswerPrimaryMaster(conn, None, test_node_uuid, test_master_list)
+        # Check that primary master was updated to known node
+        self.assertTrue(app.primary_master_node is node)
+
+    def test_initialSendPartitionTable(self):
+        client_handler = ClientEventHandler(None, self.getDispatcher())
+        conn = Mock({'getUUID': None})
+        call_list = self._testHandleUnexpectedPacketCalledWithMedhod(
+            client_handler, client_handler.handleSendPartitionTable,
+            args=(conn, None, None, None))
+        self.assertEquals(call_list[0], (conn, None))
+
+    def test_nonMasterSendPartitionTable(self):
+        for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
+            node = Mock({'getNodeType': node_type})
+            class App:
+                nm = Mock({'getNodeByUUID': node})
+                pt = None
+            app = App()
+            client_handler = ClientEventHandler(app, self.getDispatcher())
+            conn = self.getConnection()
+            client_handler.handleSendPartitionTable(conn, None, 0, [])
+            # Check that nothing happened
+            self.assertTrue(app.pt is None)
+
+    def test_newSendPartitionTable(self):
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        test_ptid = 0
+        class App:
+            nm = Mock({'getNodeByUUID': node})
+            pt = Mock({'clear': None})
+            ptid = test_ptid
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        client_handler.handleSendPartitionTable(conn, None, test_ptid + 1, [])
+        # Check that partition table got cleared and ptid got updated
+        self.assertEquals(app.ptid, test_ptid + 1)
+        self.assertEquals(len(app.pt.mockGetNamedCalls('clear')), 1)
+
+    def test_unknownNodeSendPartitionTable(self):
+        test_node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        test_ptid = 0
+        class App:
+            nm = Mock({'getNodeByUUID': ReturnValues(test_node, None), 'add': None})
+            pt = Mock({'setCell': None})
+            ptid = test_ptid
+        test_storage_uuid = self.getUUID()
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        # TODO: use realistic values
+        test_row_list = [(0, [(test_storage_uuid, 0)])]
+        client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list)
+        # Check that node got created
+        add_call_list = app.nm.mockGetNamedCalls('add')
+        self.assertEquals(len(add_call_list), 1)
+        created_node = add_call_list[0].getParam(0)
+        self.assertEqual(created_node.getUUID(), test_storage_uuid)
+        # Check that partition table cell got added
+        setCell_call_list = app.pt.mockGetNamedCalls('setCell')
+        self.assertEquals(len(setCell_call_list), 1)
+        offset = setCell_call_list[0].getParam(0)
+        node = setCell_call_list[0].getParam(1)
+        state = setCell_call_list[0].getParam(2)
+        self.assertEqual(offset, test_row_list[0][0])
+        self.assertTrue(node is created_node)
+        self.assertEqual(state, test_row_list[0][1][0][1])
+
+    def test_knownNodeSendPartitionTable(self):
+        test_node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        test_ptid = 0
+        class App:
+            nm = Mock({'getNodeByUUID': test_node, 'add': None})
+            pt = Mock({'setCell': None})
+            ptid = test_ptid
+        test_storage_uuid = self.getUUID()
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        # TODO: use realistic values
+        test_row_list = [(0, [(test_storage_uuid, 0)])]
+        client_handler.handleSendPartitionTable(conn, None, test_ptid, test_row_list)
+        # Check that node did not get created
+        self.assertEquals(len(app.nm.mockGetNamedCalls('add')), 0)
+        # Check that partition table cell got added
+        setCell_call_list = app.pt.mockGetNamedCalls('setCell')
+        self.assertEquals(len(setCell_call_list), 1)
+        offset = setCell_call_list[0].getParam(0)
+        node = setCell_call_list[0].getParam(1)
+        state = setCell_call_list[0].getParam(2)
+        self.assertEqual(offset, test_row_list[0][0])
+        self.assertTrue(node is test_node)
+        self.assertEqual(state, test_row_list[0][1][0][1])
+
+    def test_initialNotifyNodeInformation(self):
+        client_handler = ClientEventHandler(None, self.getDispatcher())
+        conn = Mock({'getUUID': None})
+        call_list = self._testHandleUnexpectedPacketCalledWithMedhod(
+            client_handler, client_handler.handleNotifyNodeInformation,
+            args=(conn, None, None))
+        self.assertEquals(call_list[0], (conn, None))
+
+    def test_nonMasterNotifyNodeInformation(self):
+        for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
+            test_master_uuid = self.getUUID()
+            node = Mock({'getNodeType': node_type})
+            class App:
+                nm = Mock({'getNodeByUUID': node})
+            app = App()
+            client_handler = ClientEventHandler(app, self.getDispatcher())
+            conn = self.getConnection(uuid=test_master_uuid)
+            client_handler.handleNotifyNodeInformation(conn, None, None)
+            # If handleNotifyNodeInformation tries to read node_list
+            # parameter, it will fail (it is  not a list).
+            # This test assumes that this failure will cause an exception to
+            # be raised, making it fail.
+
+    def test_nonIterableParameterRaisesNotifyNodeInformation(self):
+        # XXX: this test is here for sanity self-check: it verifies the
+        # assumption described in test_nonMasterNotifyNodeInformation
+        # by making a valid call with a non-iterable parameter given as
+        # node_list value.
+        test_master_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        class App:
+            nm = Mock({'getNodeByUUID': node})
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_master_uuid)
+        self.assertRaises(TypeError, client_handler.handleNotifyNodeInformation,
+            conn, None, None)
+
+    def _testNotifyNodeInformation(self, test_node, getNodeByServer=None, getNodeByUUID=MARKER):
+        invalid_uid_test_node = (test_node[0], test_node[1], test_node[2] + 1,
+                                 INVALID_UUID, test_node[4])
+        test_node_list = [test_node, invalid_uid_test_node]
+        test_master_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        if getNodeByUUID is not MARKER:
+            getNodeByUUID = ReturnValues(node, getNodeByUUID)
+        class App:
+            nm = Mock({'getNodeByUUID': getNodeByUUID,
+                       'getNodeByServer': getNodeByServer,
+                       'add': None,
+                       'remove': None})
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_master_uuid)
+        client_handler.handleNotifyNodeInformation(conn, None, test_node_list)
+        # Return nm so caller can check handler actions.
+        return app.nm
+
+    def test_unknownMasterNotifyNodeInformation(self):
+        raise NotImplementedError
+
+    def test_knownMasterNotifyNodeInformation(self):
+        raise NotImplementedError
+
+    def test_unknownStorageNotifyNodeInformation(self):
+        # TODO: use realistic value for node state
+        test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getUUID(),
+                     0)
+        nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=None)
+        # Check that node got added
+        add_call_list = nm.mockGetNamedCalls('add')
+        self.assertEqual(len(add_call_list), 1)
+        added_node = add_call_list[0].getParam(0)
+        # XXX: this test does not check that node state got updated.
+        # This is because there would be no way to tell the difference between
+        # an updated state and default state if they are the same value (we
+        # don't control node class/instance here)
+        # Likewise for server address and node uuid.
+
+    def test_knownStorageNotifyNodeInformation(self):
+        # TODO: use realistic value for node state
+        node = Mock({'setState': None, 'setServer': None})
+        test_node = (STORAGE_NODE_TYPE, '127.0.0.1', 10010, self.getUUID(),
+                     0)
+        nm = self._testNotifyNodeInformation(test_node, getNodeByUUID=node)
+        # Check that no node got added
+        self.assertEqual(len(nm.mockGetNamedCalls('add')), 0)
+        # Check that server address got set
+        setServer_call_list = node.mockGetNamedCalls('setServer')
+        self.assertEqual(len(setServer_call_list), 1)
+        self.assertEqual(setServer_call_list[0].getParam(0),
+                         (test_node[1], test_node[2]))
+        # Check node state has been updated
+        setState_call_list = node.mockGetNamedCalls('setState')
+        self.assertEqual(len(setState_call_list), 1)
+        self.assertEqual(setState_call_list[0].getParam(0), test_node[4])
+
+    def test_initialNotifyPartitionChanges(self):
+        class App:
+            nm = None
+            pt = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = Mock({'getUUID': None})
+        call_list = self._testHandleUnexpectedPacketCalledWithMedhod(
+            client_handler, client_handler.handleNotifyPartitionChanges,
+            args=(conn, None, None, None))
+        self.assertEquals(call_list[0], (conn, None))
+
+    def test_nonMasterNotifyPartitionChanges(self):
+        for node_type in (CLIENT_NODE_TYPE, STORAGE_NODE_TYPE):
+            test_master_uuid = self.getUUID()
+            node = Mock({'getNodeType': node_type, 'getUUID': test_master_uuid})
+            class App:
+                nm = Mock({'getNodeByUUID': node})
+                pt = None
+                primary_master_node = node
+            app = App()
+            client_handler = ClientEventHandler(app, self.getDispatcher())
+            conn = self.getConnection(uuid=test_master_uuid)
+            client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
+            # Check that nothing happened
+            self.assertTrue(app.pt is None)
+
+    def test_noPrimaryMasterNotifyPartitionChanges(self):
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        class App:
+            nm = Mock({'getNodeByUUID': node})
+            pt = None
+            primary_master_node = None
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection()
+        client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
+        # Check that nothing happened
+        self.assertTrue(app.pt is None)
+
+    def test_nonPrimaryMasterNotifyPartitionChanges(self):
+        test_master_uuid = self.getUUID()
+        test_sender_uuid = test_master_uuid
+        while test_sender_uuid == test_master_uuid:
+            test_sender_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE})
+        test_master_node = Mock({'getUUID': test_master_uuid})
+        class App:
+            nm = Mock({'getNodeByUUID': node})
+            pt = None
+            primary_master_node = test_master_node
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_sender_uuid)
+        client_handler.handleNotifyPartitionChanges(conn, None, 0, [])
+        # Check that nothing happened
+        self.assertTrue(app.pt is None)
+
+    def test_ignoreOutdatedPTIDNotifyPartitionChanges(self):
+        test_master_uuid = self.getUUID()
+        node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid})
+        test_ptid = 1
+        class App:
+            nm = Mock({'getNodeByUUID': node})
+            pt = None
+            primary_master_node = node
+            ptid = test_ptid
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_master_uuid)
+        client_handler.handleNotifyPartitionChanges(conn, None, test_ptid, [])
+        # Check that nothing happened
+        self.assertTrue(app.pt is None)
+        self.assertEquals(app.ptid, test_ptid)
+
+    def test_unknownNodeNotifyPartitionChanges(self):
+        test_master_uuid = self.getUUID()
+        test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid})
+        test_ptid = 1
+        class App:
+            nm = Mock({'getNodeByUUID': ReturnValues(test_node, None)})
+            pt = Mock({'setCell': None})
+            primary_master_node = test_node
+            ptid = test_ptid
+            uuid = None # XXX: Is it really needed ?
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_master_uuid)
+        test_storage_uuid = self.getUUID()
+        # TODO: use realistic values
+        test_cell_list = [(0, test_storage_uuid, 0)]
+        client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list)
+        # Check that a new node got added
+        add_call_list = app.nm.mockGetNamedCalls('add')
+        self.assertEqual(len(add_call_list), 1)
+        added_node = add_call_list[0].getParam(0)
+        # Check that partition got updated
+        self.assertEqual(app.ptid, test_ptid + 1)
+        setCell_call_list = app.pt.mockGetNamedCalls('setCell')
+        self.assertEqual(len(setCell_call_list), 1)
+        offset = setCell_call_list[0].getParam(0)
+        node = setCell_call_list[0].getParam(1)
+        state = setCell_call_list[0].getParam(2)
+        self.assertEqual(offset, test_cell_list[0][0])
+        self.assertTrue(node is added_node)
+        self.assertEqual(state, test_cell_list[0][2])
+
+    # TODO: confirm condition under which an unknown node should be added with a TEMPORARILY_DOWN_STATE (implementation is unclear)
+
+    def test_knownNodeNotifyPartitionChanges(self):
+        test_master_uuid = self.getUUID()
+        test_node = Mock({'getNodeType': MASTER_NODE_TYPE, 'getUUID': test_master_uuid})
+        test_ptid = 1
+        class App:
+            nm = Mock({'getNodeByUUID': test_node, 'add': None})
+            pt = Mock({'setCell': None})
+            primary_master_node = test_node
+            ptid = test_ptid
+        app = App()
+        client_handler = ClientEventHandler(app, self.getDispatcher())
+        conn = self.getConnection(uuid=test_master_uuid)
+        test_storage_uuid = self.getUUID()
+        # TODO: use realistic values
+        test_cell_list = [(0, test_storage_uuid, 0)]
+        client_handler.handleNotifyPartitionChanges(conn, None, test_ptid + 1, test_cell_list)
+        # Check that no node got added
+        self.assertEqual(len(app.nm.mockGetNamedCalls('add')), 0)
+        # Check that partition got updated
+        self.assertEqual(app.ptid, test_ptid + 1)
+        setCell_call_list = app.pt.mockGetNamedCalls('setCell')
+        self.assertEqual(len(setCell_call_list), 1)
+        offset = setCell_call_list[0].getParam(0)
+        node = setCell_call_list[0].getParam(1)
+        state = setCell_call_list[0].getParam(2)
+        self.assertEqual(offset, test_cell_list[0][0])
+        self.assertTrue(node is test_node)
+        self.assertEqual(state, test_cell_list[0][2])
+
+    def test_AnswerNewTID(self):
+        class App:
+            tid = None
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_tid = 1
+        client_handler.handleAnswerNewTID(conn, None, test_tid)
+        self.assertEquals(app.tid, test_tid)
+
+    def test_NotifyTransactionFinished(self):
+        class App:
+            tid = 1
+            txn_finished = None
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        client_handler.handleNotifyTransactionFinished(conn, None, 1)
+        self.assertEquals(app.txn_finished, 1)
+        # TODO: decide what to do when non-current transaction is notified as finished, and test that behaviour
+
+    def test_InvalidateObjects(self):
+        class App:
+            def _cache_lock_acquire(self):
+                pass
+
+            def _cache_lock_release(self):
+                pass
+
+            _db = Mock({'invalidate': None})
+            mq_cache = Mock({'__delitem__': None})
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_tid = 1
+        test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
+        client_handler.handleInvalidateObjects(conn, None, test_oid_list[:], test_tid)
+        # 'invalidate' is called just once
+        invalidate_call_list = app._db.mockGetNamedCalls('invalidate')
+        self.assertEquals(len(invalidate_call_list), 1)
+        invalidate_call = invalidate_call_list[0]
+        invalidate_tid = invalidate_call.getParam(0)
+        self.assertEquals(invalidate_tid, test_tid)
+        invalidate_oid_dict = invalidate_call.getParam(1)
+        self.assertEquals(len(invalidate_oid_dict), len(test_oid_list))
+        self.assertEquals(set(invalidate_oid_dict), set(test_oid_list))
+        self.assertEquals(set(invalidate_oid_dict.itervalues()), set([test_tid]))
+        # '__delitem__' is called once per invalidated object
+        delitem_call_list = app.mq_cache.mockGetNamedCalls('__delitem__')
+        self.assertEquals(len(delitem_call_list), len(test_oid_list))
+        oid_list = [x.getParam(0) for x in delitem_call_list]
+        self.assertEquals(set(oid_list), set(test_oid_list))
+
+    def test_AnswerNewOIDs(self):
+        class App:
+            new_oid_list = []
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
+        client_handler.handleAnswerNewOIDs(conn, None, test_oid_list[:])
+        self.assertEquals(set(app.new_oid_list), set(test_oid_list))
+
+    def test_StopOperation(self):
+        raise NotImplementedError
+
+    # Storage node handler
+
+    def test_AnswerObject(self):
+        class FakeLocal:
+            asked_object = ()
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        # TODO: use realistic values
+        test_object_data = ('\x00\x00\x00\x00\x00\x00\x00\x01', 0, 0, 0, 0, 'test')
+        client_handler.handleAnswerObject(conn, None, *test_object_data)
+        self.assertEquals(app.local_var.asked_object, test_object_data)
+
+    def _testAnswerStoreObject(self, app, conflicting, oid, serial):
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        client_handler.handleAnswerStoreObject(conn, None, conflicting, oid, serial)
+
+    def test_conflictingAnswerStoreObject(self):
+        class App:
+            txn_object_stored = (0, 0)
+        app = App()
+        test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
+        test_serial = 1
+        self._testAnswerStoreObject(app, 1, test_oid, test_serial)
+        self.assertEqual(app.txn_object_stored, (-1, test_serial))
+
+    def test_AnswerStoreObject(self):
+        class App:
+            txn_object_stored = (0, 0)
+        app = App()
+        test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
+        test_serial = 1
+        self._testAnswerStoreObject(app, 0, test_oid, test_serial)
+        self.assertEqual(app.txn_object_stored, (test_oid, test_serial))
+
+    def test_AnswerStoreTransaction(self):
+        class App:
+            tid = 10
+            txn_voted = 0
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_tid = 10
+        client_handler.handleAnswerStoreTransaction(conn, None, test_tid)
+        self.assertEquals(app.txn_voted, 1)
+        # TODO: test handleAnswerObject with test_tid not matching app.tid (not handled in program)
+
+    def test_AnswerTransactionInformation(self):
+        class FakeLocal:
+            txn_info = {}
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        tid = '\x00\x00\x00\x00\x00\x00\x00\x01' # TODO: use a more realistic tid
+        user = 'bar'
+        desc = 'foo'
+        ext = 0 # XXX: unused in implementation
+        oid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
+        client_handler.handleAnswerTransactionInformation(conn, None, tid, user, desc, ext, oid_list[:])
+        stored_dict = app.local_var.txn_info
+        # TODO: test 'time' value ?
+        self.assertEquals(stored_dict['user_name'], user)
+        self.assertEquals(stored_dict['description'], desc)
+        self.assertEquals(stored_dict['id'], tid)
+        self.assertEquals(stored_dict['oids'], oid_list)
+
+    def test_AnswerObjectHistory(self):
+        class FakeLocal:
+            history = (0, [])
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
+        # TODO: use realistic values
+        test_history_list = [(1, 2), (3, 4)]
+        client_handler.handleAnswerObjectHistory(conn, None, test_oid, test_history_list[:])
+        oid, history = app.local_var.history
+        self.assertEquals(oid, test_oid)
+        self.assertEquals(len(history), len(test_history_list))
+        self.assertEquals(set(history), set(test_history_list))
+
+    def test_OidNotFound(self):
+        class FakeLocal:
+            asked_object = 0
+            history = 0
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        client_handler.handleOidNotFound(conn, None, None)
+        self.assertEquals(app.local_var.asked_object, -1)
+        self.assertEquals(app.local_var.history, -1)
+
+    def test_TidNotFound(self):
+        class FakeLocal:
+            txn_info = 0
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        client_handler.handleTidNotFound(conn, None, None)
+        self.assertEquals(app.local_var.txn_info, -1)
+
+    def test_AnswerTIDs(self):
+        class FakeLocal:
+            node_tids = {}
+        class App:
+            local_var = FakeLocal()
+        app = App()
+        dispatcher = self.getDispatcher()
+        client_handler = ClientEventHandler(app, dispatcher)
+        conn = self.getConnection()
+        test_tid_list = ['\x00\x00\x00\x00\x00\x00\x00\x01', '\x00\x00\x00\x00\x00\x00\x00\x02']
+        client_handler.handleAnswerTIDs(conn, None, test_tid_list[:])
+        stored_tid_list = []
+        for tid_list in app.local_var.node_tids.itervalues():
+            stored_tid_list.extend(tid_list)
+        self.assertEquals(len(stored_tid_list), len(test_tid_list))
+        self.assertEquals(set(stored_tid_list), set(test_tid_list))
+
+if __name__ == '__main__':
+    unittest.main()
+
-- 
2.30.9