Some checks failed
CI / frontend-tests (push) Has been skipped
CI / changes (push) Successful in 10s
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (push) Successful in 7m39s
CI / backend-tests (pull_request) Successful in 7m39s
Deploy / deploy (push) Has been cancelled
Deploy / build-frontend (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
Deploy / changes (push) Successful in 6s
Дополняет движок: exporters/{dxf,pdf}.py (ezdxf + WeasyPrint lazy) + 5 тест-модулей
(geometry/placement/teap_financial/exporters/api). Не вошли в предыдущий commit
(untracked-директории).
115 lines
3.3 KiB
Python
115 lines
3.3 KiB
Python
"""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()) == {
|
|
"revenue_rub",
|
|
"cost_rub",
|
|
"gross_margin_rub",
|
|
"irr",
|
|
}
|