"""Tests for RouterContext — the central coordinator.""" import os import pytest from i2p_router.core import RouterContext from i2p_crypto.session_key_manager import SessionKeyManager from i2p_crypto.garlic_crypto import GarlicEncryptor, GarlicDecryptor from i2p_data.message_router import ( InboundMessageHandler, OutboundMessageRouter, MessageDispatcher, ) from i2p_data.garlic_handler import GarlicMessageHandler from i2p_netdb.datastore import DataStore from i2p_netdb.netdb_handler import NetDBHandler from i2p_tunnel.data_handler import TunnelCryptoRegistry, TunnelDataHandler from i2p_tunnel.build_executor import TunnelManager class TestRouterContextCreation: """RouterContext creates all subsystems on init.""" def test_creates_with_random_hash(self): ctx = RouterContext() assert isinstance(ctx.router_hash, bytes) assert len(ctx.router_hash) == 32 def test_creates_with_given_hash(self): h = os.urandom(32) ctx = RouterContext(router_hash=h) assert ctx.router_hash == h def test_owns_session_key_mgr(self): ctx = RouterContext() assert isinstance(ctx.session_key_mgr, SessionKeyManager) def test_owns_garlic_encryptor(self): ctx = RouterContext() assert isinstance(ctx.garlic_encryptor, GarlicEncryptor) def test_owns_garlic_decryptor(self): ctx = RouterContext() assert isinstance(ctx.garlic_decryptor, GarlicDecryptor) def test_owns_inbound_handler(self): ctx = RouterContext() assert isinstance(ctx.inbound_handler, InboundMessageHandler) def test_owns_outbound_router(self): ctx = RouterContext() assert isinstance(ctx.outbound_router, OutboundMessageRouter) def test_owns_message_dispatcher(self): ctx = RouterContext() assert isinstance(ctx.message_dispatcher, MessageDispatcher) def test_owns_datastore(self): ctx = RouterContext() assert isinstance(ctx.datastore, DataStore) def test_owns_netdb_handler(self): ctx = RouterContext() assert isinstance(ctx.netdb_handler, NetDBHandler) def test_owns_crypto_registry(self): ctx = RouterContext() assert isinstance(ctx.crypto_registry, TunnelCryptoRegistry) def test_owns_tunnel_data_handler(self): ctx = RouterContext() assert isinstance(ctx.tunnel_data_handler, TunnelDataHandler) def test_owns_tunnel_manager(self): ctx = RouterContext() assert isinstance(ctx.tunnel_manager, TunnelManager) def test_owns_garlic_handler(self): ctx = RouterContext() assert isinstance(ctx.garlic_handler, GarlicMessageHandler) class TestGetStatus: """get_status returns a valid dict with expected keys.""" def test_status_keys(self): ctx = RouterContext() status = ctx.get_status() assert "router_hash" in status assert "tunnels_registered" in status assert "netdb_entries" in status assert "inbound_count" in status assert "outbound_count" in status def test_status_initial_values(self): ctx = RouterContext() status = ctx.get_status() assert status["tunnels_registered"] == 0 assert status["netdb_entries"] == 0 assert status["inbound_count"] == 0 assert status["outbound_count"] == 0 def test_status_router_hash_is_hex(self): ctx = RouterContext() status = ctx.get_status() # Should be a valid hex string of the 32-byte hash assert len(status["router_hash"]) == 64 bytes.fromhex(status["router_hash"]) # should not raise class TestNetDBRoundtrip: """store_netdb_entry and lookup_netdb_entry roundtrip.""" def test_store_returns_true(self): ctx = RouterContext() key = os.urandom(32) data = b"router-info-payload" assert ctx.store_netdb_entry(key, data) is True def test_lookup_after_store(self): ctx = RouterContext() key = os.urandom(32) data = b"router-info-payload" ctx.store_netdb_entry(key, data) assert ctx.lookup_netdb_entry(key) == data def test_lookup_missing_returns_none(self): ctx = RouterContext() key = os.urandom(32) assert ctx.lookup_netdb_entry(key) is None def test_status_reflects_netdb_count(self): ctx = RouterContext() ctx.store_netdb_entry(os.urandom(32), b"a") ctx.store_netdb_entry(os.urandom(32), b"b") assert ctx.get_status()["netdb_entries"] == 2 class TestTunnelRegistration: """register_tunnel and process_tunnel_data work together.""" def test_register_and_process(self): ctx = RouterContext() tunnel_id = 42 layer_key = os.urandom(32) iv_key = os.urandom(32) # Register as endpoint so we get a "deliver" action ctx.register_tunnel(tunnel_id, layer_key, iv_key, is_endpoint=True) # Encrypted data must be multiple of 16 bytes encrypted_data = os.urandom(1024) result = ctx.process_tunnel_data(tunnel_id, encrypted_data) assert result["action"] == "deliver" assert "data" in result def test_process_unknown_tunnel(self): ctx = RouterContext() result = ctx.process_tunnel_data(999, os.urandom(1024)) assert result["action"] == "unknown" assert result["tunnel_id"] == 999 def test_status_reflects_registered_tunnels(self): ctx = RouterContext() ctx.register_tunnel(1, os.urandom(32), os.urandom(32)) ctx.register_tunnel(2, os.urandom(32), os.urandom(32)) assert ctx.get_status()["tunnels_registered"] == 2 def test_intermediate_hop_forwards(self): ctx = RouterContext() tunnel_id = 7 ctx.register_tunnel(tunnel_id, os.urandom(32), os.urandom(32), is_endpoint=False) result = ctx.process_tunnel_data(tunnel_id, os.urandom(1024)) assert result["action"] == "forward" class TestInboundDispatch: """process_inbound dispatches to registered handlers.""" def test_dispatch_delivery_status(self): ctx = RouterContext() # Delivery status: 4-byte message ID msg_id = (12345).to_bytes(4, "big") payload = msg_id + b"\x00" * 8 # Type 10 = DELIVERY_STATUS — uses default handler result = ctx.process_inbound(10, payload) assert result == 12345 def test_dispatch_unknown_type_returns_none(self): ctx = RouterContext() result = ctx.process_inbound(255, b"unknown") assert result is None def test_garlic_handler_wired(self): """GARLIC type (11) should be wired to garlic_handler.handle.""" ctx = RouterContext() # Sending garbage to garlic handler should return empty list # (tag won't be found in session key manager) result = ctx.process_inbound(11, os.urandom(128)) assert result == [] def test_database_store_wired(self): """DATABASE_STORE type (1) should be wired to netdb_handler.handle_store.""" ctx = RouterContext() key = os.urandom(32) data = b"stored-via-inbound" # Type 1 payload: key(32) || data result = ctx.process_inbound(1, key + data) assert result is True # Verify it's in the datastore assert ctx.lookup_netdb_entry(key) == data def test_database_lookup_wired(self): """DATABASE_LOOKUP type (2) should be wired to netdb_handler.handle_lookup.""" ctx = RouterContext() key = os.urandom(32) data = b"lookup-target" ctx.store_netdb_entry(key, data) # Type 2 payload: key(32) result = ctx.process_inbound(2, key) assert result == data class TestOutboundRouting: """route_outbound returns correct routing dict.""" def test_local_delivery(self): ctx = RouterContext() result = ctx.route_outbound(0, b"hello") assert result["type"] == "local" assert result["payload"] == b"hello" def test_router_delivery(self): ctx = RouterContext() rh = os.urandom(32) result = ctx.route_outbound(1, b"data", router_hash=rh) assert result["type"] == "router" assert result["router_hash"] == rh def test_tunnel_delivery(self): ctx = RouterContext() result = ctx.route_outbound( 2, b"tdata", tunnel_id=42, gateway=os.urandom(32) ) assert result["type"] == "tunnel" assert result["tunnel_id"] == 42 def test_destination_delivery(self): ctx = RouterContext() dest = os.urandom(32) result = ctx.route_outbound(3, b"dest", destination=dest) assert result["type"] == "destination" assert result["destination"] == dest class TestFullWiring: """Full wiring: store in NetDB then lookup via inbound handler.""" def test_store_then_lookup_via_inbound(self): ctx = RouterContext() key = os.urandom(32) data = b"full-wiring-test" # Store via direct API ctx.store_netdb_entry(key, data) # Lookup via inbound dispatch (type 2 = DATABASE_LOOKUP) result = ctx.process_inbound(2, key) assert result == data def test_store_via_inbound_then_lookup_direct(self): ctx = RouterContext() key = os.urandom(32) data = b"reverse-wiring-test" # Store via inbound dispatch (type 1 = DATABASE_STORE) ctx.process_inbound(1, key + data) # Lookup via direct API assert ctx.lookup_netdb_entry(key) == data def test_tunnel_registration_reflected_in_status(self): ctx = RouterContext() ctx.register_tunnel(100, os.urandom(32), os.urandom(32)) ctx.store_netdb_entry(os.urandom(32), b"x") status = ctx.get_status() assert status["tunnels_registered"] == 1 assert status["netdb_entries"] == 1