"""#2464: два места analyze_parcel глотали ошибку БД без SAVEPOINT. Найдено поиском «правило соблюдено рядом — нарушено здесь»: в самой функции `analyze_parcel` двенадцать соседних блоков оборачивают свой `db.execute` в `db.begin_nested()`, и у velocity-блока правило записано словами — «SAVEPOINT защищает outer transaction если velocity SQL падает — иначе следующие queries (_geotech_risk и пр.) крашатся с InFailedSqlTransaction» Два блока из правила выпадали: * `get_permits_nearby` (вызов на общей сессии, своей защиты не имеет, ошибку глотает вызывающий) — а после него идут `_geotech_risk`, `_neighbors_summary` и запись прогона: терялся бы весь анализ, а не блок разрешений; * `_neighbors_summary` — вызывается перед `persist_analysis_run`. Двойник сессии (`tests/support/pg_session.py`) воспроизводит семантику Postgres: обычный MagicMock aborted-состояния не имеет, и проверка «сессия осталась рабочей» на нём зелена независимо от наличия SAVEPOINT. ЧТО ЗДЕСЬ НЕ ПОКРЫТО И ПОЧЕМУ ───────────────────────────── Прямой тест есть только на `_neighbors_summary` — это отдельная функция, её можно вызвать. Блок `get_permits_nearby` лежит ВНУТРИ обработчика `analyze_parcel`, и дотянуться до него можно лишь через полный эндпоинт, чей мок-двойник сессии должен уметь отвечать на десятки разных запросов. Первая версия этого файла «покрывала» его так: тест сам писал `with db.begin_nested():` вокруг вызова и проверял, что сессия выжила. Такая проверка зелена независимо от боевого кода — она проверяет собственную обёртку. Убрана: лучше честно отсутствующее покрытие, чем поддельное. Правка там та же самая и по тому же доводу, но подтверждена чтением, а не тестом. """ from __future__ import annotations import pytest from tests.support.pg_session import AbortedTransactionError, PostgresLikeSession def test_neighbors_summary_failure_leaves_session_usable() -> None: """Сбой запроса соседей не должен помешать сохранить прогон.""" from app.api.v1 import parcels db = PostgresLikeSession() wkt = "POLYGON((60.6 56.83, 60.61 56.83, 60.61 56.84, 60.6 56.84, 60.6 56.83))" out = parcels._neighbors_summary(db, wkt, "66:41:0402029:25") assert out["data_available"] is False, "деградация должна остаться graceful" db.execute("SELECT 1") # на origin/main здесь AbortedTransactionError assert db.calls == 2 def test_double_still_poisons_without_a_savepoint() -> None: """Контроль на инструмент: без SAVEPOINT двойник ОБЯЗАН отравляться. Без этой проверки обе проверки выше были бы зелёными по построению. """ db = PostgresLikeSession() with pytest.raises(RuntimeError): db.execute("boom") with pytest.raises(AbortedTransactionError): db.execute("SELECT 1") def test_neighbors_summary_healthy_path_unchanged() -> None: """Контроль: без сбоя поведение прежнее — данные читаются, не fallback.""" from app.api.v1 import parcels db = PostgresLikeSession( fail_on=(), row={"neighbors": [], "overlap_rows": [], "neighbors_total_count": 0}, ) out = parcels._neighbors_summary(db, "POLYGON EMPTY", "66:41:0402029:25") assert out.get("data_available") is not False