"""End-to-end API test — POST /api/v1/concepts returns 3 filled variants. Goes through the FastAPI route (TestClient) and asserts the contract is populated with *real* numbers (non-zero ТЭП + financial), valid building GeoJSON, and that a degenerate parcel yields 422 rather than empty variants. """ from __future__ import annotations from fastapi.testclient import TestClient from app.main import app _PARCEL = { "type": "Polygon", "coordinates": [ [ [60.60, 56.830], [60.6045, 56.830], [60.6045, 56.8328], [60.60, 56.8328], [60.60, 56.830], ] ], } def _post(payload: dict[str, object]) -> object: client = TestClient(app) return client.post("/api/v1/concepts", json=payload) def test_concepts_returns_three_filled_variants() -> None: response = _post( { "parcel_geojson": _PARCEL, "housing_class": "comfort", "target_floors": 9, "development_type": "mid_rise", "land_cost_rub": 150_000_000, } ) assert response.status_code == 200, response.text variants = response.json()["variants"] assert len(variants) == 3 assert {v["strategy"] for v in variants} == {"max_area", "max_insolation", "balanced"} for v in variants: teap = v["teap"] fin = v["financial"] # Реальные, ненулевые числа (не stub-нули). assert teap["built_area_sqm"] > 0 assert teap["total_floor_area_sqm"] > 0 assert teap["residential_area_sqm"] > 0 assert teap["apartments_count"] > 0 assert teap["density"] > 0 assert teap["parking_spaces"] > 0 assert fin["revenue_rub"] > 0 assert fin["cost_rub"] > 0 # GeoJSON застройки непустой. fc = v["buildings_geojson"] assert fc["type"] == "FeatureCollection" assert len(fc["features"]) > 0 def test_concepts_degenerate_parcel_returns_422() -> None: tiny = { "type": "Polygon", "coordinates": [ [ [60.60, 56.830], [60.60015, 56.830], [60.60015, 56.83015], [60.60, 56.83015], [60.60, 56.830], ] ], } response = _post( { "parcel_geojson": tiny, "housing_class": "comfort", "target_floors": 9, "development_type": "mid_rise", } ) assert response.status_code == 422 def test_concepts_response_matches_contract_keys() -> None: response = _post( { "parcel_geojson": _PARCEL, "housing_class": "business", "target_floors": 16, "development_type": "high_rise", } ) assert response.status_code == 200 variant = response.json()["variants"][0] assert set(variant.keys()) == {"strategy", "buildings_geojson", "teap", "financial"} assert set(variant["teap"].keys()) == { "built_area_sqm", "total_floor_area_sqm", "residential_area_sqm", "apartments_count", "density", "parking_spaces", } assert set(variant["financial"].keys()) == { # legacy summary (backward-compat) "revenue_rub", "cost_rub", "gross_margin_rub", "irr", # revenue breakdown "revenue_residential_rub", "revenue_parking_rub", # cost cascade "construction_rub", "pir_rub", "networks_rub", "developer_services_rub", "contingency_rub", "marketing_rub", "land_rub", # БДР / taxes "vat_rub", "profit_before_tax_rub", "profit_tax_rub", "net_profit_rub", # metrics "roi", "margin_pct", "irr_is_proxy", }