gendesign/tradein-mvp/backend/tests/test_kit_registry_completeness.py
lekss361 bcdec5ebd4
All checks were successful
Deploy Trade-In / deploy (push) Successful in 58s
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 37s
Deploy Trade-In / test (push) Successful in 1m38s
Deploy Trade-In / build-backend (push) Successful in 50s
feat(rewire): scheduler_main → kit-scheduler behind USE_KIT_SCHEDULER flag, ship-dark (#2192)
2026-07-02 18:41:12 +00:00

87 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Полнота kit-registry vs боевой scheduler-dispatch (#2192).
Гарантирует ship-dark инвариант: КАЖДЫЙ source, который обрабатывает боевой
`app.services.scheduler.scheduler_loop` (if/elif + startswith), резолвится в
kit-registry (`build_registry(product_handlers=build_product_handlers(ctx))`).
Тест ПАДАЕТ, если source добавили в боевой dispatch, но забыли в registry —
иначе после переключения USE_KIT_SCHEDULER=true такой source молча не запустится.
Источник истины — сам файл боевого scheduler'а: парсим `source == "X"` (exact)
и `source.startswith("X")` (wildcard), чтобы при дрейфе dispatch тест ловил
рассинхрон автоматически, а не по захардкоженному списку.
"""
from __future__ import annotations
import os
import re
from pathlib import Path
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from scraper_kit.orchestration.scheduler import (
SchedulerContext,
build_registry,
resolve_handler,
)
import app.services.scheduler as _sched_mod
from app.services.product_handlers import build_product_handlers
from app.services.scraper_adapters import (
RealEnrichmentJobs,
RealMatcherAdapter,
RealScraperConfig,
RealSessionFactory,
)
def _production_sources() -> tuple[set[str], set[str]]:
"""(exact, prefixes) source'ов из боевого scheduler_loop dispatch."""
src = Path(_sched_mod.__file__).read_text(encoding="utf-8")
exact = set(re.findall(r'source == "([^"]+)"', src))
prefixes = set(re.findall(r'source\.startswith\("([^"]+)"\)', src))
return exact, prefixes
def _build_registry() -> dict:
ctx = SchedulerContext(
config=RealScraperConfig(),
matcher=RealMatcherAdapter(),
enrichment=RealEnrichmentJobs(),
session_factory=RealSessionFactory(),
)
return build_registry(product_handlers=build_product_handlers(ctx))
def test_production_dispatch_parse_sane() -> None:
"""Sanity: парсер видит непустой набор source'ов (защита от regex-дрейфа)."""
exact, prefixes = _production_sources()
# На момент #2192 боевой dispatch держит 25 exact + 1 wildcard-семейство.
assert len(exact) >= 25, f"parse regressed — got {len(exact)} exact sources"
assert "deactivate_stale_" in prefixes
def test_every_production_source_resolves_in_kit_registry() -> None:
"""Каждый боевой exact-source резолвится в kit-registry."""
registry = _build_registry()
exact, _ = _production_sources()
missing = [s for s in sorted(exact) if resolve_handler(s, registry) is None]
assert not missing, f"sources missing from kit registry: {missing}"
def test_wildcard_family_resolves() -> None:
"""Каждый deactivate_stale_* член семейства резолвится через wildcard-Handler."""
registry = _build_registry()
_, prefixes = _production_sources()
for prefix in sorted(prefixes):
for member in ("avito", "yandex", "cian"):
source = f"{prefix}{member}"
assert (
resolve_handler(source, registry) is not None
), f"wildcard source {source} did not resolve"
def test_unknown_source_does_not_resolve() -> None:
"""Негативный контроль: несуществующий source → None (тест реально дискриминирует)."""
registry = _build_registry()
assert resolve_handler("totally_unknown_source_xyz", registry) is None