All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 3m13s
Deploy / build-worker (push) Successful in 3m29s
Deploy / deploy (push) Successful in 1m25s
Deploy / deploy-status (push) Successful in 0s
Deploy / perimeter-smoke (push) Successful in 10s
78 lines
4.4 KiB
Python
78 lines
4.4 KiB
Python
"""#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
|