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: