"""Тесты для GET /{cad_num}/connection-capacity (Forgejo #2119 Phase A). FastAPI TestClient + mock сервиса get_connection_capacity — без Postgres. """ from __future__ import annotations from typing import Any from unittest.mock import patch import pytest from fastapi.testclient import TestClient from app.main import app _VALID_CAD = "66:41:0204016:10" def _power_point( name: str = "ПС 110/10 Уктус", reserve: float | None = 15.0, load_index: str | None = "open", distance_m: float = 850.0, ) -> dict[str, Any]: return { "name": name, "dzo_name": "Россети Урал", "voltage_class": "110/10", "load_index": load_index, "installed_capacity_mva": 40.0, "current_load_mva": 25.0, "reserve_mva": reserve, "reserve_asof": "2026-06-30", "distance_m": distance_m, "lat": 56.78, "lon": 60.61, } def _make_response( power_points: list[dict[str, Any]] | None = None, water: list[dict[str, Any]] | None = None, ) -> dict[str, Any]: if power_points is None: power_points = [_power_point()] if water is None: water = [ { "system_kind": "water", "system_name": "ЗФС, ГСВ", "reserve_thousand_m3_day": -168.5, "note": "дефицит", "period": "2026-Q1", } ] by_load_index: dict[str, int] = {} for p in power_points: li = p["load_index"] or "unknown" by_load_index[li] = by_load_index.get(li, 0) + 1 nearest = next((p for p in power_points if p["reserve_mva"] and p["reserve_mva"] > 0), None) return { "power_points": power_points, "power_summary": { "total_power_points": len(power_points), "by_load_index": by_load_index, "nearest_with_reserve": nearest, }, "water": water, } def test_connection_capacity_returns_power_and_water() -> None: full = _make_response() with patch( "app.api.v1.parcels.get_connection_capacity", return_value=full, ): client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity") assert r.status_code == 200, r.text body = r.json() assert len(body["power_points"]) == 1 assert body["power_points"][0]["voltage_class"] == "110/10" assert body["power_summary"]["total_power_points"] == 1 assert body["power_summary"]["by_load_index"]["open"] == 1 assert body["power_summary"]["nearest_with_reserve"]["reserve_mva"] == pytest.approx(15.0) # water: отрицательный резерв (дефицит) сохраняет знак. assert body["water"][0]["reserve_thousand_m3_day"] == pytest.approx(-168.5) assert body["water"][0]["system_name"] == "ЗФС, ГСВ" def test_connection_capacity_empty() -> None: empty = _make_response(power_points=[], water=[]) with patch( "app.api.v1.parcels.get_connection_capacity", return_value=empty, ): client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity") assert r.status_code == 200, r.text body = r.json() assert body["power_points"] == [] assert body["power_summary"]["total_power_points"] == 0 assert body["power_summary"]["nearest_with_reserve"] is None assert body["water"] == [] def test_connection_capacity_no_reserve_nearest_null() -> None: """Все ЦП без положительного резерва → nearest_with_reserve=null.""" pts = [_power_point(reserve=None, load_index="closed")] with patch( "app.api.v1.parcels.get_connection_capacity", return_value=_make_response(power_points=pts), ): client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity") assert r.status_code == 200, r.text body = r.json() assert body["power_summary"]["nearest_with_reserve"] is None assert body["power_summary"]["by_load_index"]["closed"] == 1 def test_connection_capacity_parcel_not_found_404() -> None: with patch( "app.api.v1.parcels.get_connection_capacity", side_effect=ValueError("Участок '66:41:9999999:1' не найден в БД"), ): client = TestClient(app) r = client.get("/api/v1/parcels/66:41:9999999:1/connection-capacity") assert r.status_code == 404 assert "не найден" in r.json()["detail"] def test_connection_capacity_passes_radius() -> None: with patch("app.api.v1.parcels.get_connection_capacity") as mock_svc: mock_svc.return_value = _make_response() client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity?radius_m=5000") assert r.status_code == 200 call = mock_svc.call_args assert call[0][2] == 5000 or call[1].get("radius_m") == 5000 def test_connection_capacity_radius_too_small_422() -> None: client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity?radius_m=50") assert r.status_code == 422 def test_connection_capacity_radius_too_large_422() -> None: client = TestClient(app) r = client.get(f"/api/v1/parcels/{_VALID_CAD}/connection-capacity?radius_m=20000") assert r.status_code == 422