"""Каденс yandex_detail_backfill и cian_detail_backfill: минуты, а не сутки. Замер прода 31.08.2026: та же дыра, что чинили для avito_detail_backfill (`test_avito_backfill_cadence.py`) — `compute_next_run_at` держит суточную гранулярность (`interval_days = max(1, ...)`), а оба хендлера были зарегистрированы без `post_claim`. Прогон Яндекса разбирает 375-450 карточек за час при очереди 11 110, Циан — ~23 с/карточку при очереди 20 501, и оба простаивали 23 часа из 24 при нуле блоков за неделю. Тесты закрепляют именно подключённость хука и разные дефолты (180 у Яндекса, 360 у Циана) — если кто-то выровняет их значения без замера, тест на дефолт это не поймает, но факт подключения и разность значений — да. Асимметрия интервалов имеет причину (Циан держит lease узла весь прогон, Яндекс — нет), см. комментарий в product_handlers.py у cian_detail_backfill. """ from __future__ import annotations from unittest.mock import MagicMock from app.services.product_handlers import build_product_handlers class _DB: """Минимальный double для post_claim-хука: перехватывает mins из execute().""" def __init__(self) -> None: self.captured: dict[str, int] = {} def execute(self, _stmt: object, params: dict[str, object]) -> None: self.captured["mins"] = int(params["mins"]) # type: ignore[arg-type] def commit(self) -> None: return None def test_yandex_detail_backfill_has_sub_daily_cadence_hook() -> None: """Без post_claim yandex_detail_backfill откатывается к суточной гранулярности.""" handlers = build_product_handlers(MagicMock()) handler = handlers["yandex_detail_backfill"] assert handler.post_claim is not None, ( "yandex_detail_backfill остался без post_claim — каденс вернулся к " "одному прогону в сутки (compute_next_run_at, interval_days >= 1)" ) def test_cian_detail_backfill_has_sub_daily_cadence_hook() -> None: """Без post_claim cian_detail_backfill откатывается к суточной гранулярности.""" handlers = build_product_handlers(MagicMock()) handler = handlers["cian_detail_backfill"] assert handler.post_claim is not None, ( "cian_detail_backfill остался без post_claim — каденс вернулся к " "одному прогону в сутки (compute_next_run_at, interval_days >= 1)" ) def test_yandex_default_interval_is_180_minutes() -> None: """Яндекс не держит lease узла (resolve_proxy_url) — можно 8 прогонов/сутки.""" handlers = build_product_handlers(MagicMock()) hook = handlers["yandex_detail_backfill"].post_claim assert hook is not None db = _DB() hook(db, 1, {}, MagicMock()) assert db.captured["mins"] == 180 def test_cian_default_interval_is_360_minutes_not_180() -> None: """Циан держит lease узла (BrowserFetcher) весь прогон — вдвое реже Яндекса. Явно проверяем НЕравенство 180: выравнивание интервалов с Яндексом отняло бы дефицитный узел (3 живых на пул) у avito/domclick_detail_backfill без замера последствий. """ handlers = build_product_handlers(MagicMock()) hook = handlers["cian_detail_backfill"].post_claim assert hook is not None db = _DB() hook(db, 1, {}, MagicMock()) assert db.captured["mins"] == 360 assert db.captured["mins"] != 180 def test_interval_minutes_from_params_wins_for_both() -> None: """Подбор интервала — через default_params расписания, без правки кода.""" handlers = build_product_handlers(MagicMock()) yandex_hook = handlers["yandex_detail_backfill"].post_claim cian_hook = handlers["cian_detail_backfill"].post_claim assert yandex_hook is not None assert cian_hook is not None db_yandex = _DB() yandex_hook(db_yandex, 1, {"interval_minutes": 90}, MagicMock()) assert db_yandex.captured["mins"] == 90 db_cian = _DB() cian_hook(db_cian, 1, {"interval_minutes": 240}, MagicMock()) assert db_cian.captured["mins"] == 240