def test_send_to_new_contact_failed_to_connect(self): """ Sending a message to a new but unreachable contact results in the resulting deferred to be resolved with the expected exception. """ nc = NetstringConnector(self.event_loop) contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:1908') msg = OK('uuid', 'recipient', 'sender', 9999, 'version', 'seal') protocol = mock.MagicMock() def side_effect(*args, **kwargs): raise ValueError() protocol.send_string = mock.MagicMock(side_effect=side_effect) sender = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) @asyncio.coroutine def faux_connect(protocol=protocol): return ('foo', protocol) with mock.patch.object(self.event_loop, 'create_connection', return_value=faux_connect()): result = nc.send(contact, msg, sender) with self.assertRaises(ValueError) as ex: self.event_loop.run_until_complete(result) self.assertEqual(1, protocol.send_string.call_count) self.assertTrue(result.done()) self.assertEqual(ex.exception, result.exception()) self.assertNotIn(contact.network_id, nc._connections)
def test_receive_valid_json_invalid_message(self): """ If a message is received that consists of valid json but a malformed message then log the incident for later analysis. """ patcher = mock.patch('drogulus.net.netstring.log.error') nc = NetstringConnector(self.event_loop) ping = { 'uuid': str(uuid.uuid4()), 'recipient': PUBLIC_KEY, 'sender': BAD_PUBLIC_KEY, 'reply_port': 1908, 'version': self.version, } seal = get_seal(ping, PRIVATE_KEY) ping['seal'] = seal ping['message'] = 'ping' raw = json.dumps(ping) sender = '192.168.0.1' handler = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) protocol = mock.MagicMock() mock_log = patcher.start() nc.receive(raw, sender, handler, protocol) self.assertEqual(3, mock_log.call_count) patcher.stop()
def test_receive_valid_json_valid_message_from_old_peer(self): """ A good message is received then the node handles the message as expected. The cached protocol object for the peer node is expired since a new protocol object is used in this instance. """ nc = NetstringConnector(self.event_loop) old_protocol = mock.MagicMock() network_id = sha512(PUBLIC_KEY.encode('ascii')).hexdigest() nc._connections[network_id] = old_protocol ok = { 'uuid': str(uuid.uuid4()), 'recipient': PUBLIC_KEY, 'sender': PUBLIC_KEY, 'reply_port': 1908, 'version': self.version, } seal = get_seal(ok, PRIVATE_KEY) ok['seal'] = seal ok['message'] = 'ok' raw = json.dumps(ok) sender = '192.168.0.1' handler = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) handler.message_received = mock.MagicMock() protocol = mock.MagicMock() nc.receive(raw, sender, handler, protocol) self.assertIn(network_id, nc._connections) self.assertEqual(nc._connections[network_id], protocol) msg = from_dict(ok) handler.message_received.assert_called_once_with(msg, 'netstring', sender, msg.reply_port)
def test_send_to_new_contact_successful_connection(self): """ Send a message to a new contact causes a new connection to be made whose associated protocol object is cached for later use. """ nc = NetstringConnector(self.event_loop) contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:1908') msg = OK('uuid', 'recipient', 'sender', 9999, 'version', 'seal') protocol = mock.MagicMock() protocol.send_string = mock.MagicMock() sender = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) @asyncio.coroutine def faux_connect(protocol=protocol): return ('foo', protocol) with mock.patch.object(self.event_loop, 'create_connection', return_value=faux_connect()): result = nc.send(contact, msg, sender) self.event_loop.run_until_complete(result) self.assertEqual(1, protocol.send_string.call_count) self.assertTrue(result.done()) self.assertEqual(True, result.result()) self.assertIn(contact.network_id, nc._connections) self.assertEqual(nc._connections[contact.network_id], protocol) expected = to_dict(msg) actual = json.loads(protocol.send_string.call_args[0][0]) self.assertEqual(expected, actual)
def test_receive_invalid_json(self): """ If a message is received that contains bad json then log the incident for later analysis. """ patcher = mock.patch('drogulus.net.netstring.log.error') nc = NetstringConnector(self.event_loop) sender = '192.168.0.1' handler = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) protocol = mock.MagicMock() raw = 'invalid JSON' mock_log = patcher.start() nc.receive(raw, sender, handler, protocol) self.assertEqual(3, mock_log.call_count) patcher.stop()
def test_init(self): """ Check the class instantiates as expected. """ nc = NetstringConnector(self.event_loop) self.assertEqual(nc._connections, {}) self.assertEqual(nc.event_loop, self.event_loop)
def test_send_with_cached_protocol(self): """ Send the message to the referenced contact using a cached protocol object. """ nc = NetstringConnector(self.event_loop) nc._send_message_with_protocol = mock.MagicMock() contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:1908') msg = OK('uuid', 'recipient', 'sender', 9999, 'version', 'seal') protocol = mock.MagicMock() sender = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) nc._connections[contact.network_id] = protocol result = nc.send(contact, msg, sender) self.assertIsInstance(result, asyncio.Future) self.assertTrue(result.done()) self.assertEqual(result.result(), True) nc._send_message_with_protocol.assert_called_once_with(msg, protocol)
def setUp(self): """ A whole bunch of generic stuff we regularly need to faff about with that are set to some sane defaults. """ loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) self.event_loop = asyncio.get_event_loop() self.connector = NetstringConnector(self.event_loop) self.version = get_version()
def test_send_message_with_protocol(self): """ Ensures that the message is translated into a dictionary and passed into the protocol object in the expected way. """ nc = NetstringConnector(self.event_loop) protocol = mock.MagicMock() protocol.send_string = mock.MagicMock() msg = OK('uuid', 'recipient', 'sender', 9999, 'version', 'seal') nc._send_message_with_protocol(msg, protocol) expected = { 'message': 'ok', 'uuid': 'uuid', 'recipient': 'recipient', 'sender': 'sender', 'reply_port': 9999, 'version': 'version', 'seal': 'seal' } actual = json.loads(protocol.send_string.call_args[0][0]) self.assertEqual(expected, actual)
def test_send_with_failing_cached_protocol(self): """ Attempting to send a message to the referenced contact using a cached protocol object that cannot send (e.g. perhaps the transport was dropped?) causes a retry as if the contact were new. """ nc = NetstringConnector(self.event_loop) contact = PeerNode(PUBLIC_KEY, self.version, 'netstring://192.168.0.1:1908') msg = OK('uuid', 'recipient', 'sender', 9999, 'version', 'seal') protocol = mock.MagicMock() def side_effect(*args, **kwargs): raise ValueError() protocol.send_string = mock.MagicMock(side_effect=side_effect) nc._connections[contact.network_id] = protocol new_protocol = mock.MagicMock() new_protocol.send_string = mock.MagicMock() sender = Node(PUBLIC_KEY, PRIVATE_KEY, self.event_loop, nc, 1908) @asyncio.coroutine def faux_connect(protocol=new_protocol): return ('foo', protocol) with mock.patch.object(self.event_loop, 'create_connection', return_value=faux_connect()): result = nc.send(contact, msg, sender) self.event_loop.run_until_complete(result) self.assertEqual(1, new_protocol.send_string.call_count) self.assertTrue(result.done()) self.assertEqual(True, result.result()) self.assertIn(contact.network_id, nc._connections) self.assertEqual(nc._connections[contact.network_id], new_protocol) expected = to_dict(msg) actual = json.loads(protocol.send_string.call_args[0][0]) self.assertEqual(expected, actual)
def start_node(event_loop, port): """ Starts a local drogulus node using throw away keys, logging to the referenced directory and listening on the referenced port. Return the Process encapsulating this node. """ connector = NetstringConnector(event_loop) private_key, public_key = get_keypair() instance = Drogulus(private_key, public_key, event_loop, connector, port) def protocol_factory(connector=connector, node=instance._node): """ Returns an appropriately configured NetstringProtocol object for each connection. """ return lambda: NetstringProtocol(connector, node) factory = protocol_factory() setup_server = event_loop.create_server(factory, port=port) event_loop.run_until_complete(setup_server) return instance