151 lines
4.9 KiB
Python
151 lines
4.9 KiB
Python
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
|