A Python port of the Invisible Internet Project (I2P)
at main 241 lines 8.8 kB view raw
1"""Tests for i2p_apps.addressbook — hostname resolution and subscription management.""" 2 3import pytest 4 5from i2p_apps.addressbook.book import Addressbook, AddressbookEntry 6from i2p_apps.addressbook.storage import FileStorage 7from i2p_apps.addressbook.subscription import SubscriptionManager 8 9 10# --------------------------------------------------------------------------- 11# Addressbook tests 12# --------------------------------------------------------------------------- 13 14class TestAddressbook: 15 """Tests for the Addressbook hostname→destination database.""" 16 17 def test_lookup_unknown_host_returns_none(self): 18 book = Addressbook() 19 assert book.lookup("unknown.i2p") is None 20 21 def test_add_entry_and_lookup(self): 22 book = Addressbook() 23 book.add_entry("example.i2p", "AAAA1234BASE64DEST") 24 assert book.lookup("example.i2p") == "AAAA1234BASE64DEST" 25 26 def test_remove_entry(self): 27 book = Addressbook() 28 book.add_entry("example.i2p", "AAAA1234BASE64DEST") 29 assert book.remove_entry("example.i2p") is True 30 assert book.lookup("example.i2p") is None 31 # Removing a non-existent entry returns False 32 assert book.remove_entry("example.i2p") is False 33 34 def test_list_all_returns_all_entries(self): 35 book = Addressbook() 36 book.add_entry("alpha.i2p", "DEST_A") 37 book.add_entry("beta.i2p", "DEST_B") 38 book.add_entry("gamma.i2p", "DEST_C") 39 all_entries = book.list_all() 40 assert len(all_entries) == 3 41 assert "alpha.i2p" in all_entries 42 assert "beta.i2p" in all_entries 43 assert "gamma.i2p" in all_entries 44 45 def test_case_insensitive_lookups(self): 46 book = Addressbook() 47 book.add_entry("Example.I2P", "DEST_CASE") 48 assert book.lookup("example.i2p") == "DEST_CASE" 49 assert book.lookup("EXAMPLE.I2P") == "DEST_CASE" 50 assert book.lookup("Example.I2P") == "DEST_CASE" 51 52 def test_duplicate_add_updates_existing(self): 53 book = Addressbook() 54 book.add_entry("example.i2p", "OLD_DEST") 55 book.add_entry("example.i2p", "NEW_DEST") 56 assert book.lookup("example.i2p") == "NEW_DEST" 57 assert book.entry_count == 1 58 59 def test_has_entry(self): 60 book = Addressbook() 61 assert book.has_entry("example.i2p") is False 62 book.add_entry("example.i2p", "DEST") 63 assert book.has_entry("example.i2p") is True 64 assert book.has_entry("EXAMPLE.I2P") is True 65 66 def test_entry_count(self): 67 book = Addressbook() 68 assert book.entry_count == 0 69 book.add_entry("a.i2p", "DEST_A") 70 book.add_entry("b.i2p", "DEST_B") 71 assert book.entry_count == 2 72 73 def test_add_entry_stores_source(self): 74 book = Addressbook() 75 book.add_entry("example.i2p", "DEST", source="subscription") 76 entries = book.list_all() 77 assert entries["example.i2p"].source == "subscription" 78 79 80# --------------------------------------------------------------------------- 81# FileStorage tests 82# --------------------------------------------------------------------------- 83 84class TestFileStorage: 85 """Tests for the FileStorage hosts.txt persistence.""" 86 87 def test_save_and_load_roundtrip(self, tmp_path): 88 path = str(tmp_path / "hosts.txt") 89 storage = FileStorage(path) 90 91 book = Addressbook() 92 book.add_entry("alpha.i2p", "DEST_ALPHA", source="local") 93 book.add_entry("beta.i2p", "DEST_BETA", source="subscription") 94 95 storage.save(book.list_all()) 96 loaded = storage.load() 97 98 assert len(loaded) == 2 99 assert loaded["alpha.i2p"].destination == "DEST_ALPHA" 100 assert loaded["beta.i2p"].destination == "DEST_BETA" 101 102 def test_empty_file_produces_empty_dict(self, tmp_path): 103 path = str(tmp_path / "hosts.txt") 104 # Create an empty file 105 with open(path, "w") as f: 106 f.write("") 107 storage = FileStorage(path) 108 loaded = storage.load() 109 assert loaded == {} 110 111 def test_missing_file_produces_empty_dict(self, tmp_path): 112 path = str(tmp_path / "nonexistent_hosts.txt") 113 storage = FileStorage(path) 114 loaded = storage.load() 115 assert loaded == {} 116 117 def test_hosts_txt_format(self, tmp_path): 118 """hosts.txt format: hostname=destination per line, # for comments.""" 119 path = str(tmp_path / "hosts.txt") 120 storage = FileStorage(path) 121 122 book = Addressbook() 123 book.add_entry("example.i2p", "BASE64DEST") 124 125 storage.save(book.list_all()) 126 127 with open(path, "r") as f: 128 content = f.read() 129 130 # Should contain hostname=destination 131 assert "example.i2p=BASE64DEST" in content 132 133 def test_load_with_comments_and_blanks(self, tmp_path): 134 path = str(tmp_path / "hosts.txt") 135 with open(path, "w") as f: 136 f.write("# This is a comment\n") 137 f.write("\n") 138 f.write("alpha.i2p=DEST_A\n") 139 f.write("# Another comment\n") 140 f.write("beta.i2p=DEST_B\n") 141 f.write("\n") 142 storage = FileStorage(path) 143 loaded = storage.load() 144 assert len(loaded) == 2 145 assert loaded["alpha.i2p"].destination == "DEST_A" 146 assert loaded["beta.i2p"].destination == "DEST_B" 147 148 def test_path_property(self, tmp_path): 149 path = str(tmp_path / "hosts.txt") 150 storage = FileStorage(path) 151 assert storage.path == path 152 153 154# --------------------------------------------------------------------------- 155# SubscriptionManager tests 156# --------------------------------------------------------------------------- 157 158class TestSubscriptionManager: 159 """Tests for the SubscriptionManager subscription list handling.""" 160 161 def test_add_subscription_url(self): 162 mgr = SubscriptionManager() 163 mgr.add_subscription("http://example.i2p/hosts.txt") 164 assert "http://example.i2p/hosts.txt" in mgr.get_subscriptions() 165 166 def test_remove_subscription(self): 167 mgr = SubscriptionManager() 168 mgr.add_subscription("http://example.i2p/hosts.txt") 169 assert mgr.remove_subscription("http://example.i2p/hosts.txt") is True 170 assert len(mgr.get_subscriptions()) == 0 171 assert mgr.remove_subscription("http://nonexistent.i2p/hosts.txt") is False 172 173 def test_parse_subscription_response(self): 174 mgr = SubscriptionManager() 175 text = ( 176 "# Subscription list\n" 177 "\n" 178 "alpha.i2p=DEST_ALPHA_BASE64\n" 179 "beta.i2p=DEST_BETA_BASE64\n" 180 "# comment line\n" 181 "gamma.i2p=DEST_GAMMA_BASE64\n" 182 ) 183 result = mgr.parse_subscription_response(text) 184 assert len(result) == 3 185 assert result["alpha.i2p"] == "DEST_ALPHA_BASE64" 186 assert result["beta.i2p"] == "DEST_BETA_BASE64" 187 assert result["gamma.i2p"] == "DEST_GAMMA_BASE64" 188 189 def test_parse_subscription_response_empty(self): 190 mgr = SubscriptionManager() 191 result = mgr.parse_subscription_response("") 192 assert result == {} 193 194 def test_merge_new_entries_into_addressbook(self): 195 mgr = SubscriptionManager() 196 book = Addressbook() 197 198 new_entries = { 199 "alpha.i2p": "DEST_A", 200 "beta.i2p": "DEST_B", 201 } 202 count = mgr.merge_into_addressbook(book, new_entries) 203 assert count == 2 204 assert book.lookup("alpha.i2p") == "DEST_A" 205 assert book.lookup("beta.i2p") == "DEST_B" 206 207 def test_existing_entries_not_overwritten_by_subscription(self): 208 mgr = SubscriptionManager() 209 book = Addressbook() 210 book.add_entry("alpha.i2p", "ORIGINAL_DEST") 211 212 new_entries = { 213 "alpha.i2p": "NEW_DEST_FROM_SUB", 214 "beta.i2p": "DEST_B", 215 } 216 count = mgr.merge_into_addressbook(book, new_entries, overwrite=False) 217 # Only beta.i2p is new 218 assert count == 1 219 # alpha.i2p should keep its original destination 220 assert book.lookup("alpha.i2p") == "ORIGINAL_DEST" 221 assert book.lookup("beta.i2p") == "DEST_B" 222 223 def test_merge_with_overwrite(self): 224 mgr = SubscriptionManager() 225 book = Addressbook() 226 book.add_entry("alpha.i2p", "ORIGINAL_DEST") 227 228 new_entries = { 229 "alpha.i2p": "OVERWRITTEN_DEST", 230 } 231 count = mgr.merge_into_addressbook(book, new_entries, overwrite=True) 232 assert count == 1 233 assert book.lookup("alpha.i2p") == "OVERWRITTEN_DEST" 234 235 def test_merge_entries_have_subscription_source(self): 236 mgr = SubscriptionManager() 237 book = Addressbook() 238 new_entries = {"example.i2p": "DEST"} 239 mgr.merge_into_addressbook(book, new_entries) 240 entries = book.list_all() 241 assert entries["example.i2p"].source == "subscription"