gendesign/backend/tests/services/test_functional_zone_lookup.py
bot-backend c93d6cbde1
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m15s
Deploy / build-worker (push) Successful in 3m14s
Deploy / deploy (push) Successful in 1m28s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 10s
fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951)
2026-08-20 07:38:23 +00:00

108 lines
3.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Тесты parcel_functional_zones (#1058) — read-side lookup функциональных зон генплана."""
from __future__ import annotations
from contextlib import contextmanager
from typing import Any
from sqlalchemy.exc import OperationalError, ProgrammingError
from app.services.site_finder.functional_zone_lookup import parcel_functional_zones
class _Result:
def __init__(self, rows: list[dict[str, Any]]) -> None:
self._rows = rows
def mappings(self) -> _Result:
return self
def all(self) -> list[dict[str, Any]]:
return self._rows
class _FakeDB:
@contextmanager
def begin_nested(self): # type: ignore[no-untyped-def]
"""#2464: lookup оборачивает свой db.execute в SAVEPOINT (сессия общая с
analyze_parcel). Здесь достаточно пустого контекст-менеджера: эти тесты про
логику самого lookup'а. Что SAVEPOINT РЕАЛЬНО откатывает aborted-транзакцию,
проверяет tests/services/site_finder/test_2464_ird_lookups_savepoint.py на
двойнике с настоящей семантикой Postgres."""
yield
def __init__(
self,
rows: list[dict[str, Any]] | None = None,
raise_exc: Exception | None = None,
) -> None:
self._rows = rows or []
self._raise = raise_exc
def execute(self, sql: Any, params: dict[str, Any] | None = None) -> _Result:
if self._raise is not None:
raise self._raise
return _Result(self._rows)
_WKT = "POLYGON((60.5 56.8, 60.6 56.8, 60.6 56.9, 60.5 56.9, 60.5 56.8))"
def test_returns_zones_for_parcel() -> None:
"""Обычный случай: строки из БД → список dict с нужными ключами."""
rows = [
{
"zone_type": "Жилые зоны",
"status": "Существующий",
"area_ha": 15.3,
"zone_class_code": "Ж",
},
{
"zone_type": "Общественные зоны",
"status": "Планируемый",
"area_ha": None,
"zone_class_code": "О",
},
]
result = parcel_functional_zones(_FakeDB(rows), _WKT)
assert len(result) == 2
first = result[0]
assert first["zone_type"] == "Жилые зоны"
assert first["status"] == "Существующий"
assert first["area_ha"] == 15.3
assert first["zone_class_code"] == "Ж"
second = result[1]
assert second["area_ha"] is None # NULL → None (не падает)
def test_empty_when_no_wkt() -> None:
"""parcel_wkt=None → пустой список без запроса к БД."""
result = parcel_functional_zones(_FakeDB([{"zone_type": "Жилые зоны"}]), None)
assert result == []
def test_empty_when_no_overlap() -> None:
"""БД вернула 0 строк → пустой список."""
result = parcel_functional_zones(_FakeDB([]), _WKT)
assert result == []
def test_graceful_when_table_missing_operational_error() -> None:
"""OperationalError (таблица не задеплоена) → [] без падения."""
db = _FakeDB(raise_exc=OperationalError("stmt", {}, Exception("no such table")))
result = parcel_functional_zones(db, _WKT)
assert result == []
def test_graceful_when_table_missing_programming_error() -> None:
"""ProgrammingError (e.g. relation не существует) → [] без падения."""
db = _FakeDB(raise_exc=ProgrammingError("stmt", {}, Exception("relation does not exist")))
result = parcel_functional_zones(db, _WKT)
assert result == []
def test_empty_string_wkt_treated_as_falsy() -> None:
"""Пустая строка wkt → пустой список (falsy guard)."""
result = parcel_functional_zones(_FakeDB([]), "")
assert result == []