from __future__ import annotations import csv import importlib.util import io import tempfile import unittest from ipaddress import IPv4Network from pathlib import Path ROOT = Path(__file__).resolve().parents[1] MODULE_PATH = ROOT / "scripts" / "sync_iana_ipv4.py" def load_module(): spec = importlib.util.spec_from_file_location("sync_iana_ipv4", MODULE_PATH) if spec is None or spec.loader is None: raise RuntimeError("cannot load sync_iana_ipv4") module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class SyncIanaIPv4Tests(unittest.TestCase): @classmethod def setUpClass(cls): cls.sync = load_module() def test_top_level_prefix_is_normalized(self): self.assertEqual( self.sync.top_level_network("001/8"), IPv4Network("1.0.0.0/8"), ) def test_registry_slug_uses_rdap_authority(self): self.assertEqual( self.sync.registry_slug("APNIC", "https://rdap.apnic.net/"), "apnic", ) self.assertEqual( self.sync.registry_slug( "Legacy allocation", "https://rdap.arin.net/registryhttp://rdap.arin.net/registry", ), "arin", ) self.assertEqual(self.sync.registry_slug("IANA - Reserved", ""), "iana") def test_rdap_url_concatenation_is_normalized(self): self.assertEqual( self.sync.clean_rdap("https://rdap.arin.net/registryhttp://rdap.arin.net/registry"), "https://rdap.arin.net/registry", ) self.assertEqual(self.sync.clean_rdap("https://rdap.apnic.net/"), "https://rdap.apnic.net/") def test_filename_sorts_numerically_in_lexical_order(self): self.assertEqual( self.sync.range_filename(IPv4Network("1.2.3.0/24"), "apnic"), "001-002-003-000--24--apnic.md", ) def test_multiple_special_blocks_are_expanded(self): self.assertEqual( self.sync.expand_address_blocks("192.0.0.170/32, 192.0.0.171/32"), [IPv4Network("192.0.0.170/32"), IPv4Network("192.0.0.171/32")], ) def test_special_block_footnote_is_ignored_for_parsing(self): self.assertEqual( self.sync.expand_address_blocks("192.0.0.0/24 [2]"), [IPv4Network("192.0.0.0/24")], ) def test_source_text_whitespace_is_normalized(self): self.assertEqual(self.sync.clean_name('"[RFC8190]\n [RFC919], Section 7"'), "[RFC8190] [RFC919], Section 7") def test_parse_top_level_requires_complete_ipv4_coverage(self): output = io.StringIO() writer = csv.DictWriter( output, fieldnames=["Prefix", "Designation", "Date", "WHOIS", "RDAP", "Status [1]", "Note"], ) writer.writeheader() for first in range(256): writer.writerow({ "Prefix": f"{first:03d}/8", "Designation": "Example", "Date": "2026-01", "WHOIS": "", "RDAP": "", "Status [1]": "RESERVED", "Note": "", }) rows = self.sync.parse_top_level_csv(output.getvalue()) self.assertEqual(len(rows), 256) self.assertEqual(rows[0]["network"], IPv4Network("0.0.0.0/8")) self.assertEqual(rows[-1]["network"], IPv4Network("255.0.0.0/8")) def test_parse_top_level_rejects_missing_slash_eight(self): sample = "Prefix,Designation,Date,WHOIS,RDAP,Status [1],Note\n000/8,IANA,1981-09,,,RESERVED,\n" with self.assertRaisesRegex(ValueError, "256"): self.sync.parse_top_level_csv(sample) def test_manual_range_file_is_not_overwritten(self): with tempfile.TemporaryDirectory() as temporary: directory = Path(temporary) path = directory / "001-000-000-000--08--apnic.md" path.write_text("# hand researched\n", encoding="utf-8") changed = self.sync.write_generated(path, "# generated\n") self.assertFalse(changed) self.assertEqual(path.read_text(encoding="utf-8"), "# hand researched\n") def test_generated_csv_uses_lf_line_endings(self): with tempfile.TemporaryDirectory() as temporary: path = Path(temporary) / "ranges.csv" self.sync.write_csv(path, ["prefix"], [{"prefix": "1.0.0.0/8"}]) self.assertNotIn(b"\r", path.read_bytes()) if __name__ == "__main__": unittest.main()