From c30581ae8d8b590ce39530abd0c32f3f4f03b298 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 4 Jul 2026 12:59:59 +0300 Subject: [PATCH] =?UTF-8?q?test(tradein/scheduler):=20=D0=BD=D0=B5=D1=82?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D0=BB=D0=BE=D0=B3=D0=B8=D1=87=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20kit=20registry-completeness=20guard=20(#2397=20P?= =?UTF-8?q?art=20C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deep-review нашёл: удалённый test_kit_registry_completeness.py заменялся test_routing_coverage_sets_match, но тот строил registry ИЗ хардкоженного _PRODUCT_SOURCES через recording-stub и проверял тот же набор — тавтология, не гоняющая реальный build_product_handlers(). Плюс _PRODUCT_SOURCES был устаревшим: не хватало cian_history_backfill, deals_freshness_monitor, osm_poi_ekb_refresh (все три есть в реальном build_product_handlers). Фикс: - _PRODUCT_SOURCES дополнен тремя пропущенными source'ами, сверен построчным grep '"...": Handler(' по product_handlers.py (20 exact + 1 wildcard). Список остаётся canonical hardcode (источник правды — сам product_handlers.py + scrape_schedules seed-миграции), с явным комментарием "поддерживается вручную". - test_routing_coverage_sets_match удалён, заменён test_real_build_product_handlers_covers_all_scheduled_sources: зовёт РЕАЛЬНЫЙ product_handlers.build_product_handlers(ctx=None) + build_registry (не stub), ассертит resolve_handler для каждого source из _PRODUCT_SOURCES ∪ _KIT_NATIVE_SOURCES. Уроненная handler-entry или scheduled source без handler'а теперь роняет CI. Регресс-пруф (сделан вручную, откачен): временно закомментировал "osm_poi_ekb_refresh" в build_product_handlers → новый тест упал с "misses source=osm_poi_ekb_refresh"; вернул строку → тест снова зелёный (git diff на product_handlers.py пуст после отката). Verify: full pytest 3179 passed / 6 skipped / 1 known-unrelated fail (test_search_cache_hit, #2208); ruff 0.7.4 чист на изменённых файлах. --- .../test_scraper_kit_scheduler_parity.py | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py b/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py index 14185704..38429830 100644 --- a/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py +++ b/tradein-mvp/backend/tests/test_scraper_kit_scheduler_parity.py @@ -5,9 +5,9 @@ Kit — единственный scheduler-путь (#2397 Part C убрал leg reap_zombies machinery; сравнивать «golden-parity» больше не с чем). Этот файл проверяет форму и инварианты kit-реестра самого по себе: - 1. REGISTRY: kit-native sweep-handler'ы зарегистрированы (`build_registry`), продуктовые - source'ы резолвятся через `resolve_handler` (включая deactivate_stale_* wildcard), - неизвестный source → None. + 1. REGISTRY: реальный `build_product_handlers()` (НЕ stub) + `build_registry` покрывает + каждый canonical scheduled source (kit-native + продуктовые + deactivate_stale_* + wildcard-члены); неизвестный source → None. 2. CLAIM: `_claim_run` — happy-path, already-running skip, lock-busy skip, appeared-under- lock rollback. Мокаем БД + ctx.runs. 3. ZOMBIE: `reap_zombies` возвращает число + commit. @@ -39,10 +39,17 @@ from scraper_kit.orchestration.scheduler import ( resolve_handler, ) -# ── продуктовые source'ы (НЕ kit-native), которые build_product_handlers регистрирует ── -# (было ключами таблицы source → старый trigger_*; сам старый dispatch удалён #2397 Part C, -# но список source-имён остаётся полезным для recording-registry ниже). +# ── продуктовые source'ы (НЕ kit-native) — CANONICAL, поддерживается ВРУЧНУЮ ───────── +# Источник истины: app.services.product_handlers.build_product_handlers() keys (минус +# wildcard "deactivate_stale_*", раскрытый в 3 конкретных member-source'а) + семейство +# scrape_schedules-сидов (data/sql/*scrape_schedules*seed*.sql, 158_seed_proxy_healthcheck_ +# schedule.sql, 162_seed_deals_freshness_monitor.sql). Этот список НЕ выводится из кода — +# добавил/убрал источник в product_handlers.py? Обнови и здесь, иначе +# test_real_build_product_handlers_covers_all_scheduled_sources ничего не поймает. +# (2026-07-04 deep-review #2397 Part C: список был устаревшим — не хватало +# cian_history_backfill/deals_freshness_monitor/osm_poi_ekb_refresh; исправлено.) _PRODUCT_SOURCES: set[str] = { + "cian_history_backfill", "rosreestr_dkp_import", "listing_source_snapshot", "asking_to_sold_ratio_refresh", @@ -53,6 +60,7 @@ _PRODUCT_SOURCES: set[str] = { "deactivate_stale_cian", "sber_index_pull", "rosreestr_quarter_poll", + "deals_freshness_monitor", "newbuilding_enrich", "yandex_newbuilding_sweep", "geoportal_coords_backfill", @@ -60,6 +68,7 @@ _PRODUCT_SOURCES: set[str] = { "avito_detail_backfill", "yandex_detail_backfill", "cadastral_geo_match", + "osm_poi_ekb_refresh", "house_imv_backfill", "house_dedup_merge", "proxy_healthcheck", @@ -123,11 +132,27 @@ def _build_recording_registry() -> tuple[dict[str, Handler], dict[str, MagicMock # ── 1. REGISTRY shape ──────────────────────────────────────────────────────── -def test_routing_coverage_sets_match() -> None: - """Множество source'ов kit-реестра ⊇ множество продуктовых source'ов.""" - registry, _ = _build_recording_registry() - for source in _PRODUCT_SOURCES: - assert resolve_handler(source, registry) is not None, f"kit misses source={source}" +def test_real_build_product_handlers_covers_all_scheduled_sources() -> None: + """Реальный (не stub!) build_product_handlers покрывает КАЖДЫЙ canonical source. + + 2026-07-04 deep-review #2397 Part C: старая версия этого теста строила registry ИЗ + `_PRODUCT_SOURCES` через recording-stub (`_build_recording_registry`) и проверяла, что + тот же набор резолвится — тавтология, ничего не гоняющая через реальный код. Эта версия + зовёт настоящий `product_handlers.build_product_handlers(ctx)` — если кто-то уронит + handler-entry в product_handlers.py (или source добавили в scrape_schedules seed, но + забыли завести handler), тест падает. + + ctx=None безопасен: build_product_handlers его не замыкает при сборке dict (сами job'ы + получают ctx только во время dispatch) — см. её докстринг + test_deals_freshness_monitor. + """ + from app.services.product_handlers import build_product_handlers + + real_registry = build_registry(build_product_handlers(ctx=None)) # type: ignore[arg-type] + + for source in _PRODUCT_SOURCES | _KIT_NATIVE_SOURCES: + assert ( + resolve_handler(source, real_registry) is not None + ), f"real build_product_handlers()/build_registry() misses source={source}" def test_kit_native_handler_set() -> None: