from fastapi.testclient import TestClient from app.main import app client = TestClient(app) def test_healthcheck() -> None: response = client.get("/health") assert response.status_code == 200 assert response.json() == {"status": "ok"} def test_openapi_contains_expected_paths() -> None: response = client.get("/openapi.json") assert response.status_code == 200 paths = response.json()["paths"] assert "/api/v1/terrain/profile" in paths assert "/api/v1/terrain/los" in paths assert "/api/v1/terrain/fresnel-slice" in paths assert "/api/v1/link/budget" in paths assert "/api/v1/coverage" in paths assert "/api/v1/status/data" in paths def test_profile_endpoint_returns_geodesic_samples() -> None: response = client.post( "/api/v1/terrain/profile", json={ "start": {"lat": 60.17, "lon": 24.94}, "end": {"lat": 60.25, "lon": 25.10}, "samples": 4, "include_buildings": True, "include_canopy": True, "k_factor": 1.333, }, ) assert response.status_code == 200 data = response.json() assert data["distance_m"] > 0 assert len(data["samples"]) == 4 assert data["path_geojson"]["type"] == "LineString" assert "data_sources" in data def test_elevation_returns_not_implemented_without_dem() -> None: response = client.get("/api/v1/terrain/elevation", params={"lat": 60.17, "lon": 24.94}) assert response.status_code == 501 assert response.json()["detail"]["code"] == "DEM_NOT_CONFIGURED" def test_profile_rejects_too_many_samples() -> None: response = client.post( "/api/v1/terrain/profile", json={ "start": {"lat": 60.17, "lon": 24.94}, "end": {"lat": 60.25, "lon": 25.10}, "samples": 2049, }, ) assert response.status_code == 422 assert response.json()["detail"]["code"] == "VALIDATION_ERROR" def test_los_endpoint_returns_obstruction_summary_fields() -> None: response = client.post( "/api/v1/terrain/los", json={ "tx": {"lat": 59.935, "lon": 30.305, "height_agl": 30}, "rx": {"lat": 59.945, "lon": 30.325, "height_agl": 2}, "frequency_mhz": 433, "fresnel_clearance": 0.6, "include_buildings": False, "include_canopy": False, "k_factor": 1.333, "samples": 16, }, ) assert response.status_code == 200 data = response.json() assert "obstructions" in data assert "geometric_obstructions" in data assert len(data["fresnel_profile"]) == 16 assert { "distance_m", "surface_m", "path_height_m", "fresnel_radius_m", "required_clearance_m", "fresnel_upper_60pct_m", "fresnel_upper_100pct_m", "fresnel_lower_60pct_m", "fresnel_lower_100pct_m", }.issubset(data["fresnel_profile"][0]) assert data["fresnel_violations_count"] == len(data["obstructions"]) assert data["geometric_obstructions_count"] == len(data["geometric_obstructions"]) assert "vegetation_loss_db" in data assert "data_sources" in data assert data["building_obstructions_count"] == len( [item for item in data["geometric_obstructions"] if item["type"] == "building"] ) def test_fresnel_slice_endpoint_returns_geojson() -> None: response = client.post( "/api/v1/terrain/fresnel-slice", json={ "tx": {"lat": 59.935, "lon": 30.305, "height_agl": 30}, "rx": {"lat": 59.945, "lon": 30.325, "height_agl": 2}, "frequency_mhz": 433, "fresnel_fraction": 0.6, "include_buildings": False, "include_canopy": False, "samples": 16, }, ) assert response.status_code == 200 data = response.json() assert data["path_geojson"]["type"] == "LineString" assert data["fresnel_geojson"]["type"] == "Feature" assert data["fresnel_geojson"]["geometry"]["type"] in {"Polygon", "MultiPolygon"} assert data["buildings_geojson"]["type"] == "FeatureCollection" assert len(data["profile"]) == 16 def test_manual_link_budget_endpoint() -> None: response = client.post( "/api/v1/link/budget", json={ "tx": {"lat": 60.17, "lon": 24.94, "height_agl": 30, "power_dbm": 37, "gain_dbi": 8}, "rx": { "lat": 60.25, "lon": 25.10, "height_agl": 2, "gain_dbi": 2, "sensitivity_dbm": -110, }, "frequency_mhz": 433, "model": "manual", "include_buildings": True, "include_vegetation": True, "k_factor": 1.333, }, ) assert response.status_code == 200 data = response.json() assert data["fspl_db"] > 0 assert "link_viable" in data assert data["fresnel_quality"] in {"clear", "partial", "blocked"} assert "data_sources" in data