A Python port of the Invisible Internet Project (I2P)
at main 154 lines 5.8 kB view raw
1"""Tests for I2PTunnel config parser — TDD: tests before implementation.""" 2 3from pathlib import Path 4 5import pytest 6 7from i2p_apps.i2ptunnel.config import TunnelConfigParser, TunnelDefinition, TunnelType 8 9 10class TestTunnelType: 11 """TunnelType enum covers all 12 Java I2P tunnel types.""" 12 13 def test_all_types_present(self): 14 expected = { 15 "httpclient", "connectclient", "client", "server", 16 "httpserver", "ircclient", "ircserver", "sockstunnel", 17 "socksirctunnel", "httpbidirserver", "streamrclient", 18 "streamrserver", 19 } 20 actual = {t.value for t in TunnelType} 21 assert actual == expected 22 23 def test_from_string(self): 24 assert TunnelType("httpclient") == TunnelType.HTTPCLIENT 25 assert TunnelType("server") == TunnelType.SERVER 26 27 28class TestTunnelDefinition: 29 """TunnelDefinition dataclass.""" 30 31 def test_defaults(self): 32 td = TunnelDefinition( 33 name="test", 34 type=TunnelType.CLIENT, 35 listen_port=1234, 36 ) 37 assert td.name == "test" 38 assert td.interface == "127.0.0.1" 39 assert td.start_on_load is False 40 assert td.shared_client is False 41 assert td.options == {} 42 assert td.proxy_list == [] 43 44 def test_is_client(self): 45 td = TunnelDefinition(name="t", type=TunnelType.HTTPCLIENT, listen_port=4444) 46 assert td.is_client is True 47 48 def test_is_server(self): 49 td = TunnelDefinition(name="t", type=TunnelType.SERVER, listen_port=0) 50 assert td.is_server is True 51 52 53class TestConfigParserLoad: 54 """Parse i2ptunnel.config Java Properties format.""" 55 56 def test_load_sample_config(self, sample_config_path: Path): 57 tunnels = TunnelConfigParser.load(sample_config_path) 58 assert len(tunnels) == 7 59 60 def test_http_proxy_parsed(self, sample_config_path: Path): 61 tunnels = TunnelConfigParser.load(sample_config_path) 62 http = tunnels[0] 63 assert http.name == "I2P HTTP Proxy" 64 assert http.type == TunnelType.HTTPCLIENT 65 assert http.interface == "127.0.0.1" 66 assert http.listen_port == 4444 67 assert http.shared_client is True 68 assert http.start_on_load is True 69 assert http.proxy_list == ["false.i2p"] 70 71 def test_options_extracted(self, sample_config_path: Path): 72 tunnels = TunnelConfigParser.load(sample_config_path) 73 http = tunnels[0] 74 assert http.options["inbound.length"] == "3" 75 assert http.options["outbound.length"] == "3" 76 77 def test_server_tunnel_parsed(self, sample_config_path: Path): 78 tunnels = TunnelConfigParser.load(sample_config_path) 79 server = tunnels[3] 80 assert server.name == "I2P Webserver" 81 assert server.type == TunnelType.HTTPSERVER 82 assert server.target_host == "127.0.0.1" 83 assert server.target_port == 7658 84 assert server.spoofed_host == "mysite.i2p" 85 assert server.priv_key_file == "eepsite/eepPriv.dat" 86 87 def test_client_tunnel_destination(self, sample_config_path: Path): 88 tunnels = TunnelConfigParser.load(sample_config_path) 89 smtp = tunnels[4] 90 assert smtp.type == TunnelType.CLIENT 91 assert smtp.target_destination == "smtp.postman.i2p" 92 assert smtp.listen_port == 7659 93 94 def test_start_on_load_false(self, sample_config_path: Path): 95 tunnels = TunnelConfigParser.load(sample_config_path) 96 irc = tunnels[2] 97 assert irc.start_on_load is False 98 99 def test_missing_properties_use_defaults(self, tmp_path: Path): 100 p = tmp_path / "minimal.config" 101 p.write_text("tunnel.0.name=Bare\ntunnel.0.type=client\n") 102 tunnels = TunnelConfigParser.load(p) 103 assert len(tunnels) == 1 104 assert tunnels[0].listen_port == 0 105 assert tunnels[0].interface == "127.0.0.1" 106 107 def test_comments_and_blank_lines_skipped(self, tmp_path: Path): 108 p = tmp_path / "comments.config" 109 p.write_text("# comment line\n\ntunnel.0.name=T\ntunnel.0.type=client\n") 110 tunnels = TunnelConfigParser.load(p) 111 assert len(tunnels) == 1 112 113 def test_nonexistent_file_returns_empty(self, tmp_path: Path): 114 tunnels = TunnelConfigParser.load(tmp_path / "nofile.config") 115 assert tunnels == [] 116 117 118class TestConfigParserLoadDir: 119 """Parse i2ptunnel.config.d/ directory.""" 120 121 def test_load_dir(self, sample_config_dir: Path): 122 tunnels = TunnelConfigParser.load_dir(sample_config_dir) 123 assert len(tunnels) == 2 124 names = {t.name for t in tunnels} 125 assert "HTTP Proxy" in names 126 assert "My Server" in names 127 128 def test_load_dir_nonexistent(self, tmp_path: Path): 129 tunnels = TunnelConfigParser.load_dir(tmp_path / "nodir") 130 assert tunnels == [] 131 132 133class TestConfigParserSave: 134 """Round-trip: load -> save -> load produces identical result.""" 135 136 def test_roundtrip(self, sample_config_path: Path, tmp_path: Path): 137 original = TunnelConfigParser.load(sample_config_path) 138 out_path = tmp_path / "output.config" 139 TunnelConfigParser.save(out_path, original) 140 reloaded = TunnelConfigParser.load(out_path) 141 assert len(reloaded) == len(original) 142 for orig, rel in zip(original, reloaded): 143 assert orig.name == rel.name 144 assert orig.type == rel.type 145 assert orig.listen_port == rel.listen_port 146 assert orig.interface == rel.interface 147 assert orig.start_on_load == rel.start_on_load 148 assert orig.options == rel.options 149 150 def test_save_empty(self, tmp_path: Path): 151 out_path = tmp_path / "empty.config" 152 TunnelConfigParser.save(out_path, []) 153 content = out_path.read_text() 154 assert content.startswith("#") # header comment