"""Каденс avito_detail_backfill: минуты, а не сутки. Замер 2026-08-22 на проде: `compute_next_run_at` имеет суточную гранулярность (`interval_days = max(1, int(...))`, целевая дата `now + interval_days`), поэтому бэкфилл получал ровно один прогон в сутки. Прогон при этом умирает по бану через 17-83 минуты — 23 часа из 24 задание простаивало, 238 обогащённых карточек за сутки при 9 951 активном объявлении. Механизм sub-hourly каденса уже существовал (`reschedule_after_minutes`, #2162, сделан для `proxy_healthcheck`) — не был подключён. Тест закрепляет именно подключённость хука, а не значение интервала: 180 минут подобраны консервативно и будут двигаться по замерам, а вот утрата хука вернёт суточный простой молча. """ from __future__ import annotations from unittest.mock import MagicMock from app.services.product_handlers import build_product_handlers def test_backfill_has_sub_daily_cadence_hook() -> None: """Без post_claim бэкфилл откатывается к суточной гранулярности.""" handlers = build_product_handlers(MagicMock()) handler = handlers["avito_detail_backfill"] assert handler.post_claim is not None, ( "avito_detail_backfill остался без post_claim — каденс вернулся к " "одному прогону в сутки (compute_next_run_at, interval_days >= 1)" ) def test_backfill_default_interval_is_sane() -> None: """Дефолт не должен быть ни суточным, ни агрессивнее получаса. Нижняя граница — не оптимум, а страховка: те же 4 прокси обслуживают SERP-свипы, и слишком частое обогащение бьёт по первичному сбору. """ handlers = build_product_handlers(MagicMock()) hook = handlers["avito_detail_backfill"].post_claim assert hook is not None captured: dict[str, int] = {} class _DB: def execute(self, _stmt: object, params: dict[str, object]) -> None: captured["mins"] = int(params["mins"]) # type: ignore[arg-type] def commit(self) -> None: return None # params без ключа → сработает дефолт фабрики, его и проверяем. hook(_DB(), 1, {}, MagicMock()) assert 30 <= captured["mins"] <= 360, ( f"интервал по умолчанию {captured['mins']} мин вне разумного коридора" ) assert captured["mins"] < 1440, "интервал в сутки и больше — это исходный дефект" def test_interval_minutes_from_params_wins() -> None: """Подбор интервала — через default_params расписания, без правки кода.""" handlers = build_product_handlers(MagicMock()) hook = handlers["avito_detail_backfill"].post_claim assert hook is not None captured: dict[str, int] = {} class _DB: def execute(self, _stmt: object, params: dict[str, object]) -> None: captured["mins"] = int(params["mins"]) # type: ignore[arg-type] def commit(self) -> None: return None hook(_DB(), 1, {"interval_minutes": 90}, MagicMock()) assert captured["mins"] == 90