From 12e2a9949d9aa316871e556b790d986f085886da Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 12 Sep 2026 16:53:09 +0300 Subject: [PATCH 1/2] =?UTF-8?q?perf(tests):=20=D0=B7=D0=B0=D0=B3=D0=BB?= =?UTF-8?q?=D1=83=D1=88=D0=B8=D1=82=D1=8C=20=D1=84=D0=BE=D1=80=D1=81=D0=B0?= =?UTF-8?q?=D0=B9=D1=82-enqueue=20=D0=B2=20analyze-=D1=82=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=85=20Site=20Finder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit analyze_parcel() в конце безусловно дёргает forecast_site_finder_report.delay(...) (best-effort §22-форсайт, обёрнут в try/except). В тестовой песочнице Celery-брокер (Redis) недоступен, и .delay() синхронно ждёт kombu reconnect-backoff (~69с) прежде чем исключение будет проглочено — эта пауза сидела внутри КАЖДОГО теста, дёргающего POST /analyze без мока форсайт-таски (9 файлов tests/api/v1/test_analyze_*.py, test_2464*.py — 32 теста, ~2218 из 2503с полного прогона). Добавлена autouse-фикстура в tests/api/v1/conftest.py по образцу уже существующей _stub_zone_regulation_resolver — глушит .delay в no-op MagicMock для всех тестов каталога. Тесты самого enqueue (test_parcels_forecast.py, test_run_history_and_response_contract.py) переопределяют тот же target своим per-test patch поверх авто-фикстуры (nested mock-scope), их проверки не затронуты. Проверено: 7 целевых файлов (32 теста) — 36.7с суммарно вместо таймаутов по 90с на файл, все зелёные. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG --- backend/tests/api/v1/conftest.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/tests/api/v1/conftest.py b/backend/tests/api/v1/conftest.py index 463d56ea..f5741b19 100644 --- a/backend/tests/api/v1/conftest.py +++ b/backend/tests/api/v1/conftest.py @@ -17,12 +17,22 @@ Analyze-тесты с ПОЗИЦИОННЫМ DB-моком (``_make_db_for_analy (``test_analyze_zoning_regulation.py``), переопределяют этот же target своим per-test ``patch`` — он применяется ПОВЕРХ авто-фикстуры (вложенный mock-scope), так что их ожидаемые значения резолвера сохраняются. + +Perf-fix (2026-09-12): в конце ``analyze_parcel`` — безусловный best-effort +``forecast_site_finder_report.delay(...)`` (§22-форсайт enqueue, см. app/api/v1/parcels.py). +В песочнице тестов Celery-брокер (Redis) недоступен → ``.delay()`` синхронно ждёт +kombu-реконнект с растущим backoff (~69с) ДО того как try/except его проглотит — +эта пауза оказалась внутри КАЖДОГО теста, который дергает ``POST /analyze`` и не +мокал форсайт-таску. Авто-фикстура ниже глушит ``.delay`` в no-op-мок для ВСЕХ +тестов каталога (как и с резолвером выше) — тесты самого enqueue +(``test_parcels_forecast.py``, ``test_run_history_and_response_contract.py``) +переопределяют тот же target своим per-test ``patch`` поверх авто-фикстуры. """ from __future__ import annotations from collections.abc import Iterator -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest @@ -37,3 +47,15 @@ def _stub_zone_regulation_resolver() -> Iterator[None]: """ with patch("app.api.v1.parcels.get_or_fetch_zone_regulation", return_value=None): yield + + +@pytest.fixture(autouse=True) +def _stub_forecast_enqueue() -> Iterator[None]: + """No-op форсайт-enqueue по умолчанию (без реального Celery/Redis round-trip). + + ``.delay(...)`` в проде — fire-and-forget (best-effort, обёрнут в try/except в + ``analyze_parcel``), тестам сам форсайт не нужен, а живой брокер в CI/локальной + песочнице недоступен и держит запрос ~69с на реконнект-backoff. + """ + with patch("app.workers.tasks.forecast.forecast_site_finder_report.delay", MagicMock()): + yield -- 2.45.3 From 464e9e1c67f68777a27f25797d8cd04e7ef6b24b Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 12 Sep 2026 17:01:22 +0300 Subject: [PATCH 2/2] =?UTF-8?q?perf(tests):=20=D1=81=D1=85=D0=BB=D0=BE?= =?UTF-8?q?=D0=BF=D0=BD=D1=83=D1=82=D1=8C=20inline-=D0=BE=D0=B6=D0=B8?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20NSPD-=D1=84=D0=B5=D1=82=D1=87?= =?UTF-8?q?=D0=B0=20=D0=B2=20analyze-=D1=82=D0=B5=D1=81=D1=82=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Два теста (`test_market_price_invalid_cad_returns_404`, `test_recent_permits_invalid_cad_no_regression`) спали ровно по 16с: ветка «участка нет в БД» в `analyze_parcel` ждёт появления геометрии циклом `sleep(_INLINE_FETCH_POLL_INTERVAL_S)` до `_INLINE_FETCH_WAIT_S` (15с), а в тестах фетч замокан и геометрия не появляется никогда. Autouse-фикстура каталога ужимает окно до 0.05с при шаге опроса 0.01с. Цикл остаётся рабочим (несколько итераций), а не выключается нулём — тесты fast-path'а продолжают видеть опросы; тесты с собственным patch того же имени переопределяют фикстуру поверх. tests/api/v1 целиком: 898с → 7.7с, 377 passed. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01JY6iWDnGDthdvsMWgK1BMG --- backend/tests/api/v1/conftest.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/backend/tests/api/v1/conftest.py b/backend/tests/api/v1/conftest.py index f5741b19..33df9426 100644 --- a/backend/tests/api/v1/conftest.py +++ b/backend/tests/api/v1/conftest.py @@ -59,3 +59,22 @@ def _stub_forecast_enqueue() -> Iterator[None]: """ with patch("app.workers.tasks.forecast.forecast_site_finder_report.delay", MagicMock()): yield + + +@pytest.fixture(autouse=True) +def _fast_inline_fetch_wait(monkeypatch: pytest.MonkeyPatch) -> None: + """Схлопнуть inline-ожидание NSPD-фетча (#93 graceful fallback) до миллисекунд. + + В ``analyze_parcel`` ветка «участка нет в БД» ждёт появления геометрии циклом + ``sleep(_INLINE_FETCH_POLL_INTERVAL_S)`` до ``_INLINE_FETCH_WAIT_S`` (15с прод- + значение). В тестах фетч замокан и геометрия не появится никогда → каждый такой + тест честно спал 16с (``test_market_price_invalid_cad_returns_404``, + ``test_recent_permits_invalid_cad_no_regression``). + + Оставляем цикл РАБОЧИМ (несколько итераций по 10мс), а не выключаем его нулём: + тесты, проверяющие сам fast-path «строка появилась на N-м опросе», продолжают + видеть опросы. Тесты с собственным ``patch`` того же имени (напр. + ``test_run_history_and_response_contract.py``) переопределяют это поверх. + """ + monkeypatch.setattr("app.api.v1.parcels._INLINE_FETCH_WAIT_S", 0.05) + monkeypatch.setattr("app.api.v1.parcels._INLINE_FETCH_POLL_INTERVAL_S", 0.01) -- 2.45.3