From 7b0827499a281df1af73c832871d138a44932965 Mon Sep 17 00:00:00 2001 From: "ashkan@beigi.net" Date: Thu, 2 Jul 2026 02:53:25 +0000 Subject: [PATCH] Test: cover client-IP/ARP resolution and SAIP schema lookup Make get_mac_from_arp take an arp_path for testability (default unchanged), and add tests for get_client_ip (X-Forwarded-For vs remote_addr), ARP MAC resolution (match/no-match/missing), SAIP decode-unavailable, and the SAIP_ASN_PATH override. Coverage: utils 100%, overall 93%; 108 tests pass. Co-Authored-By: Claude Opus 4.8 --- .coverage | Bin 0 -> 53248 bytes server/utils.py | 6 +++--- tests/test_saip.py | 16 ++++++++++++++++ tests/test_utils.py | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 .coverage diff --git a/.coverage b/.coverage new file mode 100644 index 0000000000000000000000000000000000000000..946a518e2b2016880da97d059381993c5f32caa2 GIT binary patch literal 53248 zcmeI4PmCK^9mi)pV~@vP`z?)D&a#PiKrNe9ch_u7O0x}YLmR1SH!Ue?;S^_VKYNGT zGtSI7**%2CNl-a(gbPG!FGQjjE-e>O!ykzt4n>Fy2SU?Jh(M(39}p-c%kRyfwY}M` zYC^Q?_B*mYGw;3M@BMzC_j~X8jXm>~BMX+#jhf>&P2YG>8kS^Pdekr^Nz&<^qj$V% zbfCs3w3mC1H#^j&nXf*XW6w)Nsjo@wSniW-dZ<41PWJrJ!}@bsOPkVXC<7Y^fB*=9 zz_uaKIWnXbMn~l{r+u^B;J#~CxEp@v9((rSfujeFqX)jUaL@?Tj6E5Hw&`i(fZ;l) zj23r|n$_TjWmm0=>05T)@E3Vpy6y3*n6oI2W-U>0CXg3LC96tOKChFCmTNUlciDJ> zFHZ#pqS}1Ezx zuWX+;l+bnMP>mE-9Jea0FT18)S>)c7VXk>tamdakKlIjhnnm9+8kQZnX?vD$Ikv%< zc%|+0>V^(Z325;~@Or%=UB`t($3w}et{ysF;SD6C;*oHTjI*5>YgbTIO~QfAcDq?7 z+$TcUADBCObVHj~^;)^{RjKXlt{(3V|)`(G zW}&kd0;Sot!%Sy1r4`1;%=<-1ORRJdk|QAQ?+^N6EO4 zL^ zMbi_bkT+qJ9jC!fJDeLx;*dvmTAmTuaQmz@-__wQ3mx^76M;?fT%HfiXc~oiaV6;&RH2=F-40BDT2Rs!+`nloOAkp*NEg$_K@c zXyVeW_!O(;wD>C58JD%f{rAhAcGQEz!KviYH-V-Z`x>1l`XXyxW;e*5p;4N?BJ~EB z0N%q;oQ5@s5ixA`a)>T26@5XFM>E7FbM#VSAPwdk2OMb|Q?>Cr9Ip4FVLx=yR;ahKLk z^z00mp8ZiZTRz>|UaYyC@2ywpuJwh?oojPi+}>OFNZTX2BAp)8w6@*7xYgzIV^u59e>L=x;1Mv#RaNP~u!VA^j_J;dhs6XWwP} zU%mKaXJzi@!{@KaJ9Wx^K(qCKe)rb7iT%IOzyI>YZ&sxntD{<)a)wiHtnSoAk>s(Y z^!|NI*XM7&b#32YynV+HuNHGD%Gs59<-BwA@@nU@mJ|}%2X<+yI8fbMTA_n`lnZYc zK9iuMrxUmK4)6TtFW)=y6aNo4mVR*M>YuM)TD|CHZ=Lz|>8n5e;_KhIHz}*So=*h# z|FhR5c7wgo{?4w_Re;~JU$LLjb$~yzH$TQr4A(&b1V8`;KmY_l00ck)1V8`;Kwzs0 zh^qmzo(*>COt91RV3$e=Fr;E&v3-|4YML?GWk*0T2KI z5C8!X009sH0T2KI5C8!XxSIsT@Bdi;-%Y~M4FVtl0w4eaAOHd&00JNY0w4eaTTejT j|Bv str: return request.remote_addr or "" -def get_mac_from_arp(ip: str) -> Optional[str]: - """Attempt to resolve a MAC address from /proc/net/arp for the given IP.""" +def get_mac_from_arp(ip: str, arp_path: str = "/proc/net/arp") -> Optional[str]: + """Attempt to resolve a MAC address from the ARP table for the given IP.""" try: - with open("/proc/net/arp") as f: + with open(arp_path) as f: for line in f: parts = line.split() if len(parts) >= 4 and parts[0] == ip: diff --git a/tests/test_saip.py b/tests/test_saip.py index cca17e6..95badd1 100644 --- a/tests/test_saip.py +++ b/tests/test_saip.py @@ -41,6 +41,22 @@ class TestSaipEncoding: saip.create_saip_profile("302720000000001", generate_ki(), b"\x00" * 16, generate_iccid()) + def test_decode_unavailable_raises(self, monkeypatch): + monkeypatch.setattr(saip, "_compiler", lambda: None) + with pytest.raises(RuntimeError, match="unavailable"): + saip.decode_saip_profile(b"\x00") + + def test_find_asn_honours_env_override(self, tmp_path, monkeypatch): + fake = tmp_path / "schema.asn" + fake.write_text("-- placeholder\n") + monkeypatch.setenv("SAIP_ASN_PATH", str(fake)) + assert saip.find_saip_asn() == str(fake) + + def test_find_asn_ignores_missing_override(self, monkeypatch): + monkeypatch.setenv("SAIP_ASN_PATH", "/no/such/schema.asn") + # Falls through to the pySim lookup (or None); never returns the bad path. + assert saip.find_saip_asn() != "/no/such/schema.asn" + @saip_required def test_produces_der_bytes(self): ki = generate_ki() diff --git a/tests/test_utils.py b/tests/test_utils.py index 31b3caa..22c4bd6 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,8 @@ import pytest from server.utils import ( ensure_parent_dir, + get_client_ip, + get_mac_from_arp, load_config, normalize_mac, validate_imsi, @@ -10,6 +12,41 @@ from server.utils import ( ) +class _FakeRequest: + def __init__(self, headers=None, remote_addr=None): + self.headers = headers or {} + self.remote_addr = remote_addr + + +class TestClientResolution: + def test_client_ip_prefers_forwarded(self): + req = _FakeRequest(headers={"X-Forwarded-For": "203.0.113.7, 10.0.0.1"}, + remote_addr="10.0.0.1") + assert get_client_ip(req) == "203.0.113.7" + + def test_client_ip_falls_back_to_remote_addr(self): + assert get_client_ip(_FakeRequest(remote_addr="192.168.4.10")) == "192.168.4.10" + + def test_client_ip_empty_when_unknown(self): + assert get_client_ip(_FakeRequest()) == "" + + def test_mac_from_arp_match(self, tmp_path): + arp = tmp_path / "arp" + arp.write_text( + "IP address HW type Flags HW address Mask Device\n" + "192.168.4.10 0x1 0x2 AA:BB:CC:DD:EE:FF * wlan0\n" + ) + assert get_mac_from_arp("192.168.4.10", str(arp)) == "aa:bb:cc:dd:ee:ff" + + def test_mac_from_arp_no_match(self, tmp_path): + arp = tmp_path / "arp" + arp.write_text("IP address ...\n192.168.4.99 0x1 0x2 11:22:33:44:55:66 * wlan0\n") + assert get_mac_from_arp("192.168.4.10", str(arp)) is None + + def test_mac_from_arp_missing_file(self): + assert get_mac_from_arp("192.168.4.10", "/no/such/arp") is None + + class TestEnsureParentDir: def test_bare_filename_does_not_crash(self): # No directory component -> must be a no-op, not FileNotFoundError.