""" Unit tests for line-of-sight and Fresnel zone calculations. """ import sys import os import math sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..')) from app.geometry.los import fresnel_radius, check_los_terrain def freq_to_wavelength(freq_mhz): return 300.0 / freq_mhz class TestFresnelRadius: def test_positive_result(self): r = fresnel_radius(500, 500, freq_to_wavelength(1800)) assert r > 0 def test_symmetric(self): wl = freq_to_wavelength(900) r1 = fresnel_radius(300, 700, wl) r2 = fresnel_radius(700, 300, wl) assert abs(r1 - r2) < 0.001 def test_lower_freq_larger_radius(self): r_high = fresnel_radius(500, 500, freq_to_wavelength(1800)) r_low = fresnel_radius(500, 500, freq_to_wavelength(900)) assert r_low > r_high def test_center_is_maximum(self): """Fresnel radius is largest at the midpoint of the path.""" wl = freq_to_wavelength(900) r_center = fresnel_radius(500, 500, wl) r_offset = fresnel_radius(200, 800, wl) assert r_center > r_offset def test_known_value(self): """First Fresnel zone radius at midpoint of 1km path at 1GHz ~ 8.66m.""" # F1 = sqrt(lambda * d1 * d2 / (d1+d2)) # lambda = 0.3m at 1000MHz, d1=d2=500m # F1 = sqrt(0.3 * 500 * 500 / 1000) = sqrt(75) ~ 8.66m r = fresnel_radius(500, 500, freq_to_wavelength(1000)) assert 8.0 < r < 9.5 def test_zero_distance(self): r = fresnel_radius(0, 500, freq_to_wavelength(900)) assert r == 0.0 class TestCheckLosTerrain: def test_flat_terrain_has_los(self): profile = [ {"elevation": 100, "distance": 0}, {"elevation": 100, "distance": 250}, {"elevation": 100, "distance": 500}, {"elevation": 100, "distance": 750}, {"elevation": 100, "distance": 1000}, ] result = check_los_terrain(profile, tx_height=30, rx_height=1.5) assert result["has_los"] is True assert result["clearance"] > 0 def test_hill_blocks_los(self): profile = [ {"elevation": 100, "distance": 0}, {"elevation": 100, "distance": 250}, {"elevation": 200, "distance": 500}, # 100m hill {"elevation": 100, "distance": 750}, {"elevation": 100, "distance": 1000}, ] result = check_los_terrain(profile, tx_height=10, rx_height=1.5) assert result["has_los"] is False assert result["blocked_at"] is not None def test_empty_profile(self): result = check_los_terrain([], tx_height=30, rx_height=1.5) assert result["has_los"] is True def test_high_antenna_clears_hill(self): profile = [ {"elevation": 100, "distance": 0}, {"elevation": 110, "distance": 500}, {"elevation": 100, "distance": 1000}, ] # TX at 150m (100+50), RX at 101.5m. LOS at 500m ≈ 125.75m, terrain=110m → clear result = check_los_terrain(profile, tx_height=50, rx_height=1.5) assert result["has_los"] is True if __name__ == "__main__": for cls in [TestFresnelRadius, TestCheckLosTerrain]: instance = cls() for method_name in [m for m in dir(instance) if m.startswith("test_")]: try: getattr(instance, method_name)() print(f" PASS {cls.__name__}.{method_name}") except Exception as e: print(f" FAIL {cls.__name__}.{method_name}: {e}") print("\nAll tests completed.")