"""Tests for RouterConfig — configuration loading, validation, and env overrides.""" import os import pytest from i2p_router.config import RouterConfig class TestDefaultConfig: """Default config values are sensible.""" def test_default_router_name(self): cfg = RouterConfig() assert cfg.router_name == "i2p-python-router" def test_default_listen_host(self): cfg = RouterConfig() assert cfg.listen_host == "0.0.0.0" def test_default_listen_port(self): cfg = RouterConfig() assert cfg.listen_port == 9700 def test_default_tunnel_counts(self): cfg = RouterConfig() assert cfg.inbound_tunnel_count == 3 assert cfg.outbound_tunnel_count == 3 def test_default_tunnel_length(self): cfg = RouterConfig() assert cfg.tunnel_length == 3 def test_default_tunnel_lifetime(self): cfg = RouterConfig() assert cfg.tunnel_lifetime_seconds == 600 def test_default_bandwidth_unlimited(self): cfg = RouterConfig() assert cfg.bandwidth_limit_kbps == 0 def test_default_floodfill_off(self): cfg = RouterConfig() assert cfg.floodfill is False def test_default_reseed_urls_empty(self): cfg = RouterConfig() assert cfg.reseed_urls == [] def test_default_timeouts(self): cfg = RouterConfig() assert cfg.handshake_timeout_seconds == 30 assert cfg.idle_timeout_seconds == 300 def test_default_data_dir(self): cfg = RouterConfig() assert cfg.data_dir == "~/.i2p-python" class TestFromDict: """Config loading from dict.""" def test_from_dict_overrides_defaults(self): cfg = RouterConfig.from_dict({ "router_name": "my-router", "listen_port": 8000, "floodfill": True, }) assert cfg.router_name == "my-router" assert cfg.listen_port == 8000 assert cfg.floodfill is True # Non-overridden fields keep defaults assert cfg.listen_host == "0.0.0.0" assert cfg.tunnel_length == 3 def test_from_dict_empty_gives_defaults(self): cfg = RouterConfig.from_dict({}) default = RouterConfig() assert cfg.router_name == default.router_name assert cfg.listen_port == default.listen_port def test_from_dict_all_fields(self): d = { "router_name": "test", "listen_host": "127.0.0.1", "listen_port": 7777, "inbound_tunnel_count": 5, "outbound_tunnel_count": 5, "tunnel_length": 2, "tunnel_lifetime_seconds": 300, "bandwidth_limit_kbps": 1024, "floodfill": True, "reseed_urls": ["https://example.com/reseed"], "handshake_timeout_seconds": 10, "idle_timeout_seconds": 120, "data_dir": "/tmp/i2p-test", } cfg = RouterConfig.from_dict(d) for key, val in d.items(): assert getattr(cfg, key) == val def test_from_dict_ignores_unknown_keys(self): cfg = RouterConfig.from_dict({"unknown_key": "value"}) assert cfg.router_name == "i2p-python-router" class TestValidation: """Invalid config raises ValueError.""" def test_negative_port_raises(self): cfg = RouterConfig(listen_port=-1) with pytest.raises(ValueError, match="listen_port"): cfg.validate() def test_port_too_high_raises(self): cfg = RouterConfig(listen_port=70000) with pytest.raises(ValueError, match="listen_port"): cfg.validate() def test_zero_port_is_valid(self): cfg = RouterConfig(listen_port=0) cfg.validate() # Should not raise def test_max_port_is_valid(self): cfg = RouterConfig(listen_port=65535) cfg.validate() # Should not raise def test_negative_inbound_tunnel_count_raises(self): cfg = RouterConfig(inbound_tunnel_count=-1) with pytest.raises(ValueError, match="inbound_tunnel_count"): cfg.validate() def test_negative_outbound_tunnel_count_raises(self): cfg = RouterConfig(outbound_tunnel_count=-1) with pytest.raises(ValueError, match="outbound_tunnel_count"): cfg.validate() def test_negative_tunnel_length_raises(self): cfg = RouterConfig(tunnel_length=-1) with pytest.raises(ValueError, match="tunnel_length"): cfg.validate() def test_negative_tunnel_lifetime_raises(self): cfg = RouterConfig(tunnel_lifetime_seconds=-1) with pytest.raises(ValueError, match="tunnel_lifetime_seconds"): cfg.validate() def test_negative_bandwidth_raises(self): cfg = RouterConfig(bandwidth_limit_kbps=-1) with pytest.raises(ValueError, match="bandwidth_limit_kbps"): cfg.validate() def test_negative_handshake_timeout_raises(self): cfg = RouterConfig(handshake_timeout_seconds=-1) with pytest.raises(ValueError, match="handshake_timeout_seconds"): cfg.validate() def test_negative_idle_timeout_raises(self): cfg = RouterConfig(idle_timeout_seconds=-1) with pytest.raises(ValueError, match="idle_timeout_seconds"): cfg.validate() def test_valid_config_does_not_raise(self): cfg = RouterConfig() cfg.validate() # Should not raise class TestEnvOverrides: """Environment variable overrides.""" def test_env_router_name(self, monkeypatch): monkeypatch.setenv("I2P_ROUTER_NAME", "env-router") cfg = RouterConfig.with_env_overrides() assert cfg.router_name == "env-router" def test_env_listen_host(self, monkeypatch): monkeypatch.setenv("I2P_LISTEN_HOST", "127.0.0.1") cfg = RouterConfig.with_env_overrides() assert cfg.listen_host == "127.0.0.1" def test_env_listen_port(self, monkeypatch): monkeypatch.setenv("I2P_LISTEN_PORT", "8080") cfg = RouterConfig.with_env_overrides() assert cfg.listen_port == 8080 def test_env_tunnel_length(self, monkeypatch): monkeypatch.setenv("I2P_TUNNEL_LENGTH", "5") cfg = RouterConfig.with_env_overrides() assert cfg.tunnel_length == 5 def test_env_floodfill(self, monkeypatch): monkeypatch.setenv("I2P_FLOODFILL", "true") cfg = RouterConfig.with_env_overrides() assert cfg.floodfill is True def test_env_floodfill_false(self, monkeypatch): monkeypatch.setenv("I2P_FLOODFILL", "false") cfg = RouterConfig.with_env_overrides() assert cfg.floodfill is False def test_env_data_dir(self, monkeypatch): monkeypatch.setenv("I2P_DATA_DIR", "/opt/i2p") cfg = RouterConfig.with_env_overrides() assert cfg.data_dir == "/opt/i2p" def test_env_overrides_with_base_config(self, monkeypatch): monkeypatch.setenv("I2P_LISTEN_PORT", "7777") base = RouterConfig(router_name="base-router", listen_port=5000) cfg = RouterConfig.with_env_overrides(base) # Env override wins over base assert cfg.listen_port == 7777 # Non-overridden base value preserved assert cfg.router_name == "base-router" def test_env_bandwidth_limit(self, monkeypatch): monkeypatch.setenv("I2P_BANDWIDTH_LIMIT_KBPS", "512") cfg = RouterConfig.with_env_overrides() assert cfg.bandwidth_limit_kbps == 512 def test_no_env_vars_gives_defaults(self): # Ensure no I2P_ env vars are set for this test cfg = RouterConfig.with_env_overrides() default = RouterConfig() assert cfg.router_name == default.router_name assert cfg.listen_port == default.listen_port